pax_global_header00006660000000000000000000000064141306107770014520gustar00rootroot0000000000000052 comment=bac679d5f5a252e479a576855c8b00bad9b67d6a openmw-openmw-0.47.0/000077500000000000000000000000001413061077700144205ustar00rootroot00000000000000openmw-openmw-0.47.0/.editorconfig000066400000000000000000000003531413061077700170760ustar00rootroot00000000000000root = 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.47.0/.gitignore000066400000000000000000000024171413061077700164140ustar00rootroot00000000000000## 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 data 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 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.47.0/.gitlab-ci.yml000066400000000000000000000270071413061077700170620ustar00rootroot00000000000000# Note: We set `needs` on each job to control the job DAG. # See https://docs.gitlab.com/ee/ci/yaml/#needs stages: - build .Debian_Image: tags: - docker - linux image: debian:bullseye .Debian: extends: .Debian_Image cache: paths: - apt-cache/ - ccache/ stage: build script: - 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) - cmake --install . - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi - ccache -s artifacts: paths: - build/install/ Coverity: extends: .Debian_Image stage: build rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - 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: - CI/before_script.linux.sh # Add more than just `openmw` once we can build everything under 3h - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw 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="`git describe --tags`" --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" variables: CC: gcc CXX: g++ timeout: 8h Debian_GCC: extends: .Debian cache: key: Debian_GCC.v2 before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc CXX: g++ CCACHE_SIZE: 3G # 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 Debian_GCC_tests: extends: Debian_GCC cache: key: Debian_GCC_tests.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 Debian_GCC_Static_Deps: extends: Debian_GCC cache: key: Debian_GCC_Static_Deps paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 Debian_GCC_Static_Deps_tests: extends: Debian_GCC_Static_Deps cache: key: Debian_GCC_Static_Deps_tests variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 Debian_Clang: extends: .Debian before_script: - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: key: Debian_Clang.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: 2h Debian_Clang_tests: extends: Debian_Clang cache: key: Debian_Clang_tests.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 .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" macOS11_Xcode12: extends: .MacOS image: macos-11-xcode-12 allow_failure: true cache: key: macOS11_Xcode12.v1 variables: CCACHE_SIZE: 3G macOS10.15_Xcode11: extends: .MacOS image: macos-10.15-xcode-11 cache: key: macOS10.15_Xcode11.v1 variables: CCACHE_SIZE: 3G variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" package: "Engine" variables: &cs-targets targets: "openmw-cs,bsatool,esmtool,niftest" package: "CS" variables: &tests-targets targets: "openmw_test_suite,openmw_detournavigator_navmeshtilescache_benchmark" package: "Tests" .Windows_Ninja_Base: tags: - windows 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 install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -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 stage: build script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) - 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 - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${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-v2 paths: - 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 Windows_Ninja_Engine_Release: extends: - .Windows_Ninja_Base variables: <<: *engine-targets config: "Release" Windows_Ninja_Engine_Debug: extends: - .Windows_Ninja_Base variables: <<: *engine-targets config: "Debug" Windows_Ninja_Engine_RelWithDebInfo: extends: - .Windows_Ninja_Base variables: <<: *engine-targets config: "RelWithDebInfo" Windows_Ninja_CS_Release: extends: - .Windows_Ninja_Base variables: <<: *cs-targets config: "Release" Windows_Ninja_CS_Debug: extends: - .Windows_Ninja_Base variables: <<: *cs-targets config: "Debug" Windows_Ninja_CS_RelWithDebInfo: extends: - .Windows_Ninja_Base variables: <<: *cs-targets config: "RelWithDebInfo" Windows_Ninja_Tests_RelWithDebInfo: extends: .Windows_Ninja_Base stage: build variables: <<: *tests-targets 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 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 install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install vswhere -y - choco install python -y - refreshenv stage: build script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - 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 - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${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-v2 paths: - 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 Windows_MSBuild_Engine_Release: extends: - .Windows_MSBuild_Base variables: <<: *engine-targets config: "Release" Windows_MSBuild_Engine_Debug: extends: - .Windows_MSBuild_Base variables: <<: *engine-targets config: "Debug" Windows_MSBuild_Engine_RelWithDebInfo: extends: - .Windows_MSBuild_Base variables: <<: *engine-targets config: "RelWithDebInfo" Windows_MSBuild_CS_Release: extends: - .Windows_MSBuild_Base variables: <<: *cs-targets config: "Release" Windows_MSBuild_CS_Debug: extends: - .Windows_MSBuild_Base variables: <<: *cs-targets config: "Debug" Windows_MSBuild_CS_RelWithDebInfo: extends: - .Windows_MSBuild_Base variables: <<: *cs-targets config: "RelWithDebInfo" Windows_MSBuild_Tests_RelWithDebInfo: extends: .Windows_MSBuild_Base stage: build variables: <<: *tests-targets config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" Debian_AndroidNDK_arm64-v8a: tags: - linux image: debian:bullseye variables: CCACHE_SIZE: 3G cache: key: Debian_AndroidNDK_arm64-v8a.v3 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections - apt-get update -yq - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer stage: build script: - 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 . - ccache -s 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 openmw-openmw-0.47.0/.travis.yml000066400000000000000000000046501413061077700165360ustar00rootroot00000000000000language: cpp branches: only: - master - /openmw-.*$/ cache: ccache addons: apt: sources: - sourceline: 'ppa:openmw/openmw' packages: [ # Dev build-essential, cmake, clang-tools-9, ccache, # Boost libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, # Audio, Video and Misc. deps libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev, # The other ones from OpenMW ppa libbullet-dev, libopenscenegraph-dev, libmygui-dev ] matrix: include: - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6 os: osx osx_image: xcode11.6 - name: OpenMW (all) on Ubuntu Focal with GCC os: linux dist: focal - name: OpenMW (tests only) on Ubuntu Focal with GCC os: linux dist: focal env: - BUILD_TESTS_ONLY: 1 - name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis os: linux dist: focal env: - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9" - ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9" compiler: clang before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi - ./CI/before_install.${TRAVIS_OS_NAME}.sh before_script: - ccache -z - ./CI/before_script.${TRAVIS_OS_NAME}.sh script: - cd ./build - ${ANALYZE} make -j3; - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - cd "${TRAVIS_BUILD_DIR}" - ccache -s deploy: provider: script script: ./CI/deploy.osx.sh skip_cleanup: true on: branch: master condition: "$TRAVIS_EVENT_TYPE = cron && $TRAVIS_OS_NAME = osx" repo: OpenMW/openmw notifications: email: if: repository_slug = OpenMW/openmw AND branch = master recipients: - corrmage+travis-ci@gmail.com on_success: change on_failure: always irc: if: repository_slug = OpenMW/openmw AND branch = master channels: - "irc.libera.chat#openmw" on_success: change on_failure: always use_notice: true openmw-openmw-0.47.0/AUTHORS.md000066400000000000000000000170401413061077700160710ustar00rootroot00000000000000Contributors ============ 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) Allofich Andrei Kortunov (akortunov) AnyOldName3 Ardekantur Armin Preiml Artem Kotsynyak (greye) Artem Nykolenko (anikm21) artemutin Arthur Moore (EmperorArthur) Assumeru athile Aussiemon Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks 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) Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) eroen escondida Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) Finbar Crago (finbar-crago) 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 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) 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 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 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) nobrakal Nolan Poe (nopoe) 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) 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 Tom Mason (wheybags) Torben Leif Carrington (TorbenC) unelsson uramer viadanna Vincent Heuken Vladimir Panteleev (CyberShadow) Wang Ryu (bzzt) Will Herrmann (Thunderforge) vocollapse xyzz Yohaulticetl Yuri Krupenin zelurker Noah Gooder Andrew Appuhamy (andrew-app) 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 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.47.0/CHANGELOG.md000066400000000000000000006060111413061077700162350ustar00rootroot000000000000000.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 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.47.0/CHANGELOG_PR.md000066400000000000000000000070541413061077700166400ustar00rootroot00000000000000*** 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.47.0/CI/000077500000000000000000000000001413061077700147135ustar00rootroot00000000000000openmw-openmw-0.47.0/CI/ActivateMSVC.ps1000066400000000000000000000015701413061077700175740ustar00rootroot00000000000000& "${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.47.0/CI/activate_msvc.sh000066400000000000000000000030641413061077700201020ustar00rootroot00000000000000#!/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.47.0/CI/before_install.android.sh000077500000000000000000000004261413061077700216630ustar00rootroot00000000000000#!/bin/sh -ex curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null openmw-openmw-0.47.0/CI/before_install.linux.sh000077500000000000000000000001731413061077700214010ustar00rootroot00000000000000#!/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.47.0/CI/before_install.osx.sh000077500000000000000000000020351413061077700210520ustar00rootroot00000000000000#!/bin/sh -ex # workaround python issue on travis [ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true [ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true [ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true # Some of these tools can come from places other than brew, so check before installing 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 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-20210617.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null # additional libraries [ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfigopenmw-openmw-0.47.0/CI/before_script.android.sh000077500000000000000000000012621413061077700215200ustar00rootroot00000000000000#!/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 mkdir -p build cd build cmake \ -DCMAKE_TOOLCHAIN_FILE=/usr/lib/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ -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 \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_OSG=OFF \ -DOPENMW_USE_SYSTEM_BULLET=OFF \ .. openmw-openmw-0.47.0/CI/before_script.linux.sh000077500000000000000000000027371413061077700212470ustar00rootroot00000000000000#!/bin/bash set -xeo pipefail free -m BUILD_UNITTESTS=OFF BUILD_BENCHMARKS=OFF if [[ "${BUILD_TESTS_ONLY}" ]]; then export GOOGLETEST_DIR="${PWD}/googletest/build/install" env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh BUILD_UNITTESTS=ON BUILD_BENCHMARKS=ON fi 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 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON -DCMAKE_INSTALL_PREFIX=install ) 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 ) fi mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then ${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_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ .. else ${ANALYZE} cmake \ "${CMAKE_CONF_OPTS[@]}" \ .. fi openmw-openmw-0.47.0/CI/before_script.msvc.sh000066400000000000000000000731621413061077700210550ustar00rootroot00000000000000#!/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="" KEEP="" UNITY_BUILD="" VS_VERSION="" NMAKE="" NINJA="" PDBS="" PLATFORM="" CONFIGURATIONS=() TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." BUILD_BENCHMARKS="" 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 ;; 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 ;; 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. -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 <2017/2019> 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 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 --retry 10 -Ly 5 -o $FILE $URL || RET=$? else RET=0 curl --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="2017" fi case $VS_VERSION in 16|16.0|2019 ) GENERATOR="Visual Studio 16 2019" TOOLSET="vc142" MSVC_REAL_VER="16" MSVC_VER="14.2" MSVC_YEAR="2015" MSVC_REAL_YEAR="2019" MSVC_DISPLAY_YEAR="2019" BOOST_VER="1.71.0" BOOST_VER_URL="1_71_0" BOOST_VER_SDK="107100" ;; 15|15.0|2017 ) GENERATOR="Visual Studio 15 2017" TOOLSET="vc141" MSVC_REAL_VER="15" MSVC_VER="14.1" MSVC_YEAR="2015" MSVC_REAL_YEAR="2017" MSVC_DISPLAY_YEAR="2017" BOOST_VER="1.67.0" BOOST_VER_URL="1_67_0" BOOST_VER_SDK="106700" ;; 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 [ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ]; then GENERATOR="${GENERATOR} Win64" fi 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 [ $MSVC_REAL_VER -ge 16 ] && [ -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 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${MSVC_YEAR}-win${BITS}-double.7z" \ "Bullet-2.89-msvc${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.0" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \ "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" if [ -n "$PDBS" ]; then download "MyGUI symbols" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \ "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" fi # OpenAL download "OpenAL-Soft 1.20.1" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \ "OpenAL-Soft-1.20.1.zip" # OSG download "OpenSceneGraph 3.6.5" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \ "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" if [ -n "$PDBS" ]; then download "OpenSceneGraph symbols" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \ "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" fi # SDL2 download "SDL 2.0.12" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \ "SDL2-2.0.12.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" # Google test and mock if [ ! -z $TEST_FRAMEWORK ]; then echo "Google test 1.10.0..." if [ -d googletest ]; then printf " Google test exists, skipping." else git clone -b release-1.10.0 https://github.com/google/googletest.git fi fi 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${MSVC_YEAR}-win${BITS}-double.7z" $STRIP mv "Bullet-2.89-msvc${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.0... " { 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 0" 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.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP mv "MyGUI-3.4.0-msvc${MSVC_REAL_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.20.1... " { if [ -d openal-soft-1.20.1-bin ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf openal-soft-1.20.1-bin eval 7z x -y OpenAL-Soft-1.20.1.zip $STRIP fi OPENAL_SDK="$(real_pwd)/openal-soft-1.20.1-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.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" done echo Done. } cd $DEPS echo # OSG printf "OSG 3.6.5... " { 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-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP mv "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}" 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" else SUFFIX="" fi add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \ "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,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 if [ -z $APPVEYOR ]; then printf "Qt 5.15.0... " else printf "Qt 5.13 AppVeyor... " fi { if [ $BITS -eq 64 ]; then SUFFIX="_64" else SUFFIX="" fi if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL qt_version="5.15.0" if [ "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" == "win64_msvc2017_64" ]; then echo "This combination of options is known not to work. Falling back to Qt 5.14.2." qt_version="5.14.2" fi QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_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${MSVC_REAL_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. else QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}" 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 DIR=$(windowsPathAsUnix "${QT_SDK}") add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. fi } cd $DEPS echo # SDL2 printf "SDL 2.0.12... " { if [ -d SDL2-2.0.12 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf SDL2-2.0.12 eval 7z x -y SDL2-2.0.12.zip $STRIP fi export SDL2DIR="$(real_pwd)/SDL2-2.0.12" for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/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 # Google Test and Google Mock if [ ! -z $TEST_FRAMEWORK ]; then printf "Google test 1.10.0 ..." cd googletest mkdir -p build${MSVC_REAL_YEAR} cd build${MSVC_REAL_YEAR} GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest" for CONFIGURATION in ${CONFIGURATIONS[@]}; do # FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one. GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" ) if [ $GTEST_CONFIG == "Debug" ]; then DEBUG_SUFFIX="d" else DEBUG_SUFFIX="" fi if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then # Always use MSBuild solution files as they don't need the environment activating cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_REAL_YEAR$([ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ] && echo " Win64")" $([ $MSVC_REAL_VER -ge 16 ] && echo "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")") -DBUILD_SHARED_LIBS=1 cmake --build . --config "${GTEST_CONFIG}" cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}" fi add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll" add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll" add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll" add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll" done add_cmake_opts -DBUILD_UNITTESTS=yes # FindGTest and FindGMock do not work perfectly on Windows # but we can help them by telling them everything we know about installation add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT" add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT" add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib" add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib" add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib" add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib" add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib" add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib" add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib" add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib" add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED echo Done. fi 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}" 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 "$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.47.0/CI/before_script.osx.sh000077500000000000000000000013741413061077700207150ustar00rootroot00000000000000#!/bin/sh -e export CXX=clang++ export CC=clang 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="10.14" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D BUILD_OPENMW=TRUE \ -D BUILD_OPENCS=TRUE \ -D BUILD_ESMTOOL=TRUE \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ -G"Unix Makefiles" \ .. openmw-openmw-0.47.0/CI/build.msvc.sh000066400000000000000000000031351413061077700173170ustar00rootroot00000000000000#!/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.47.0/CI/build_googletest.sh000077500000000000000000000010031413061077700205770ustar00rootroot00000000000000#!/bin/sh -ex git clone -b release-1.10.0 https://github.com/google/googletest.git cd googletest mkdir build cd build cmake \ -D CMAKE_C_COMPILER="${CC}" \ -D CMAKE_CXX_COMPILER="${CXX}" \ -D CMAKE_C_COMPILER_LAUNCHER=ccache \ -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ -D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \ -G "${GENERATOR}" \ .. cmake --build . --config "${CONFIGURATION}" -- -j $(nproc) cmake --install . --config "${CONFIGURATION}" openmw-openmw-0.47.0/CI/check_package.osx.sh000077500000000000000000000011111413061077700206040ustar00rootroot00000000000000#!/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.47.0/CI/check_tabs.sh000077500000000000000000000003011413061077700173320ustar00rootroot00000000000000#!/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.47.0/CI/deploy.osx.sh000077500000000000000000000024501413061077700173570ustar00rootroot00000000000000#!/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.47.0/CI/install_debian_deps.sh000077500000000000000000000034171413061077700212420ustar00rootroot00000000000000#!/bin/bash set -euo pipefail print_help() { echo "usage: $0 [group]..." echo echo " available groups: "${!GROUPED_DEPS[@]}"" } declare -rA GROUPED_DEPS=( [gcc]="binutils gcc g++ libc-dev" [clang]="binutils clang" # Common dependencies for building OpenMW. [openmw-deps]=" make cmake ccache git pkg-config 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 ca-certificates " # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" [coverity]="curl" # 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]=" make cmake ccache curl unzip libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev " ) 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" set -x mkdir -pv "$APT_CACHE_DIR" apt-get update -yq apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" openmw-openmw-0.47.0/CMakeLists.txt000066400000000000000000001162531413061077700171700ustar00rootroot00000000000000project(OpenMW) cmake_minimum_required(VERSION 3.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 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() 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) 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 47) 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_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) endif() # Set up common paths if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") 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_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/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(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files") 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() # 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) # Instruct CMake to run moc automatically when needed. #set(CMAKE_AUTOMOC ON) 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) 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 -DWIN32_LEAN_AND_MEAN) 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.4.0 REQUIRED ${USED_OSG_COMPONENTS}) if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5) message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).") endif() 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() 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(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.2.2 REQUIRED) endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) include_directories( BEFORE SYSTEM "." ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} ${BULLET_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) set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR}) set(OPENMW_SHADERS_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 "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long") 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() # Components add_subdirectory (components) target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") # 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 (WIN32) if (MSVC) if (OPENMW_MP_BUILD) set( MT_BUILD "/MP") endif() 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 ) 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) set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") 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} ${MT_BUILD}") endif() if (BUILD_ESMTOOL) set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_ESSIMPORTER) set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_LAUNCHER) set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_MWINIIMPORTER) set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_OPENCS) set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_OPENMW) if (OPENMW_UNITY_BUILD) set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") else() set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() endif() if (BUILD_WIZARD) set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() if (BUILD_BENCHMARKS) set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") 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() # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4) message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4") 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) 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 "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".") 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) # Install licenses INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) # 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.47.0/CONTRIBUTING.md000066400000000000000000000247371413061077700166660ustar00rootroot00000000000000How 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 priviledges are required. If you do not have the priviledges, 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.47.0/LICENSE000066400000000000000000001045111413061077700154270ustar00rootroot00000000000000 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.47.0/README.md000066400000000000000000000167401413061077700157070ustar00rootroot00000000000000OpenMW ====== [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master) OpenMW is an open-source 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.47.0 * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) * Website: https://www.openmw.org * IRC: #openmw on irc.libera.chat Font Licenses: * DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.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://wiki.openmw.org/index.php?title=Installation_Instructions) * [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.47.0/apps/000077500000000000000000000000001413061077700153635ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/benchmarks/000077500000000000000000000000001413061077700175005ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/benchmarks/CMakeLists.txt000066400000000000000000000021141413061077700222360ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.11) set(BENCHMARK_ENABLE_TESTING OFF) set(BENCHMARK_ENABLE_INSTALL OFF) set(BENCHMARK_ENABLE_GTEST_TESTS OFF) set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") include(FetchContent) FetchContent_Declare(benchmark URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 SOURCE_DIR fetched/benchmark ) FetchContent_MakeAvailableExcludeFromAll(benchmark) set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}") 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() openmw-openmw-0.47.0/apps/benchmarks/detournavigator/000077500000000000000000000000001413061077700227155ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/benchmarks/detournavigator/navmeshtilescache.cpp000066400000000000000000000212211413061077700271050ustar00rootroot00000000000000#include #include #include #include #include namespace { using namespace DetourNavigator; struct Key { osg::Vec3f mAgentHalfExtents; TilePosition mTilePosition; RecastMesh mRecastMesh; std::vector mOffMeshConnections; }; struct Item { Key mKey; NavMeshData mValue; }; template TilePosition generateTilePosition(int max, Random& random) { std::uniform_int_distribution distribution(0, max); return TilePosition(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, [&] { const btVector3 shift(distribution(random), distribution(random), distribution(random)); return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)}; }); } template void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, count, [&] { const osg::Vec3f start(distribution(random), distribution(random), distribution(random)); const osg::Vec3f end(distribution(random), distribution(random), distribution(random)); return OffMeshConnection {start, end, generateAreaType(random)}; }); } template Key generateKey(std::size_t triangles, Random& random) { const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); const TilePosition tilePosition = generateTilePosition(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); std::vector vertices; generateVertices(std::back_inserter(vertices), triangles * 1.98, random); std::vector indices; generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, vertices.size() * 1.53, random); std::vector areaTypes; generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); std::vector water; generateWater(std::back_inserter(water), 2, random); RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices), std::move(areaTypes), std::move(water)); std::vector offMeshConnections; generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random); return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)}; } constexpr std::size_t trianglesPerTile = 310; 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.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); *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.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections); benchmark::DoNotOptimize(result); } } constexpr auto getFromFilledCache_1m_100hit = getFromFilledCache<1 * 1024 * 1024, 100>; constexpr auto getFromFilledCache_4m_100hit = getFromFilledCache<4 * 1024 * 1024, 100>; constexpr auto getFromFilledCache_16m_100hit = getFromFilledCache<16 * 1024 * 1024, 100>; constexpr auto getFromFilledCache_64m_100hit = getFromFilledCache<64 * 1024 * 1024, 100>; constexpr auto getFromFilledCache_1m_70hit = getFromFilledCache<1 * 1024 * 1024, 70>; constexpr auto getFromFilledCache_4m_70hit = getFromFilledCache<4 * 1024 * 1024, 70>; constexpr auto getFromFilledCache_16m_70hit = getFromFilledCache<16 * 1024 * 1024, 70>; constexpr auto getFromFilledCache_64m_70hit = getFromFilledCache<64 * 1024 * 1024, 70>; 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.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); benchmark::DoNotOptimize(result); } } constexpr auto setToBoundedNonEmptyCache_1m = setToBoundedNonEmptyCache<1 * 1024 * 1024>; constexpr auto setToBoundedNonEmptyCache_4m = setToBoundedNonEmptyCache<4 * 1024 * 1024>; constexpr auto setToBoundedNonEmptyCache_16m = setToBoundedNonEmptyCache<16 * 1024 * 1024>; constexpr auto setToBoundedNonEmptyCache_64m = setToBoundedNonEmptyCache<64 * 1024 * 1024>; } // 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.47.0/apps/bsatool/000077500000000000000000000000001413061077700170265ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/bsatool/CMakeLists.txt000066400000000000000000000005421413061077700215670ustar00rootroot00000000000000set(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() openmw-openmw-0.47.0/apps/bsatool/bsatool.cpp000066400000000000000000000234501413061077700212010ustar00rootroot00000000000000#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; } int list(std::unique_ptr& bsa, Arguments& info); int extract(std::unique_ptr& bsa, Arguments& info); int extractAll(std::unique_ptr& bsa, Arguments& info); int add(std::unique_ptr& bsa, Arguments& info); int main(int argc, char** argv) { try { Arguments info; if(!parseOptions (argc, argv, info)) return 1; // Open file std::unique_ptr bsa; Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename); if (bsaVersion == Bsa::BSAVER_COMPRESSED) bsa = std::make_unique(Bsa::CompressedBSAFile()); else bsa = std::make_unique(Bsa::BSAFile()); 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; } } catch (std::exception& e) { std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; return 2; } } int list(std::unique_ptr& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &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; } 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, "\\", "/"); if (!bsa->exists(archivePath.c_str())) { 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; } // Get a stream for the file to extract Files::IStreamPtr stream = bsa->getFile(archivePath.c_str()); 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; } 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 // (inefficient because getFile iter on the list again) Files::IStreamPtr data = bsa->getFile(file.name()); 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; } 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; } openmw-openmw-0.47.0/apps/doc.hpp000066400000000000000000000001151413061077700166360ustar00rootroot00000000000000// Note: This is not a regular source file. /// \defgroup apps Applications openmw-openmw-0.47.0/apps/esmtool/000077500000000000000000000000001413061077700170455ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/esmtool/.gitignore000066400000000000000000000000531413061077700210330ustar00rootroot00000000000000*.esp *.esm *.ess *_test esmtool *_raw.txt openmw-openmw-0.47.0/apps/esmtool/CMakeLists.txt000066400000000000000000000005721413061077700216110ustar00rootroot00000000000000set(ESMTOOL esmtool.cpp labels.hpp labels.cpp record.hpp record.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() openmw-openmw-0.47.0/apps/esmtool/esmtool.cpp000066400000000000000000000443171413061077700212440ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "record.hpp" #define ESMTOOL_VERSION 1.2 // Create a local alias for brevity namespace bpo = boost::program_options; struct ESMData { std::string author; std::string description; unsigned int version; std::vector masters; std::deque mRecords; // Value: (Reference, Deleted flag) std::map > > mCellRefs; std::map mRecordStats; static const std::set sLabeledRec; }; static const int sLabeledRecIds[] = { ESM::REC_GLOB, ESM::REC_CLAS, ESM::REC_FACT, ESM::REC_RACE, ESM::REC_SOUN, ESM::REC_REGN, ESM::REC_BSGN, ESM::REC_LTEX, ESM::REC_STAT, ESM::REC_DOOR, ESM::REC_MISC, ESM::REC_WEAP, ESM::REC_CONT, ESM::REC_SPEL, ESM::REC_CREA, ESM::REC_BODY, ESM::REC_LIGH, ESM::REC_ENCH, ESM::REC_NPC_, ESM::REC_ARMO, ESM::REC_CLOT, ESM::REC_REPA, ESM::REC_ACTI, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_INGR, ESM::REC_BOOK, ESM::REC_ALCH, ESM::REC_LEVI, ESM::REC_LEVC, ESM::REC_SNDG, ESM::REC_CELL, ESM::REC_DIAL }; const std::set ESMData::sLabeledRec = std::set(sLabeledRecIds, sLabeledRecIds + 34); // Based on the legacy struct struct Arguments { bool raw_given; bool quiet_given; bool loadcells_given; bool plain_given; std::string mode; std::string encoding; std::string filename; std::string outname; std::vector types; std::string name; ESMData data; ESM::ESMReader reader; ESM::ESMWriter writer; }; 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", "Show an unformatted list of all records and subrecords.") // 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 " << ESMTOOL_VERSION << std::endl; return false; } if (!variables.count("mode")) { std::cout << "No mode specified!" << std::endl << std::endl << 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 << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl << 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]; info.raw_given = variables.count ("raw") != 0; 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." << std::endl; info.encoding = "win1252"; } std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; return true; } void printRaw(ESM::ESMReader &esm); void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info); int load(Arguments& info); int clone(Arguments& info); int comp(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); 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; } void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) { 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; while(cell.getNextRef(esm, ref, deleted)) { if (save) { info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); } if(quiet) continue; std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl; std::cout << " ID: " << ref.mRefID << std::endl; std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")" << std::endl; if (ref.mScale != 1.f) std::cout << " Scale: " << ref.mScale << std::endl; if (!ref.mOwner.empty()) std::cout << " Owner: " << ref.mOwner << std::endl; if (!ref.mGlobalVariable.empty()) std::cout << " Global: " << ref.mGlobalVariable << std::endl; if (!ref.mFaction.empty()) std::cout << " Faction: " << ref.mFaction << std::endl; if (!ref.mFaction.empty() || ref.mFactionRank != -2) std::cout << " Faction rank: " << ref.mFactionRank << std::endl; std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << std::endl; std::cout << " Uses/health: " << ref.mChargeInt << std::endl; std::cout << " Gold value: " << ref.mGoldValue << std::endl; std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << std::endl; std::cout << " Deleted: " << deleted << std::endl; if (!ref.mKey.empty()) std::cout << " Key: " << ref.mKey << std::endl; std::cout << " Lock level: " << ref.mLockLevel << std::endl; if (!ref.mTrap.empty()) std::cout << " Trap: " << ref.mTrap << std::endl; if (!ref.mSoul.empty()) std::cout << " Soul: " << ref.mSoul << std::endl; if (ref.mTeleport) { std::cout << " Destination position: (" << ref.mDoorDest.pos[0] << ", " << ref.mDoorDest.pos[1] << ", " << ref.mDoorDest.pos[2] << ")" << std::endl; if (!ref.mDestCell.empty()) std::cout << " Destination cell: " << ref.mDestCell << std::endl; } } } void printRaw(ESM::ESMReader &esm) { while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); std::cout << "Record: " << n.toString() << std::endl; 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.toString() << " - " << esm.getSubSize() << " bytes @ 0x" << std::hex << offs << "\n"; std::cout.flags(f); } } } int load(Arguments& info) { ESM::ESMReader& esm = info.reader; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); std::string filename = info.filename; std::cout << "Loading file: " << filename << std::endl; std::unordered_set skipped; try { if(info.raw_given && info.mode == "dump") { std::cout << "RAW file listing:\n"; esm.openRaw(filename); printRaw(esm); return 0; } bool quiet = (info.quiet_given || info.mode == "clone"); bool loadCells = (info.loadcells_given || info.mode == "clone"); bool save = (info.mode == "clone"); esm.open(filename); info.data.author = esm.getAuthor(); info.data.description = esm.getDesc(); info.data.masters = esm.getGameFiles(); if (!quiet) { std::cout << "Author: " << esm.getAuthor() << std::endl << "Description: " << esm.getDesc() << std::endl << "File format version: " << esm.getFVer() << std::endl; std::vector masterData = esm.getGameFiles(); if (!masterData.empty()) { std::cout << "Masters:" << std::endl; for(const auto& master : masterData) std::cout << " " << master.name << ", " << master.size << " bytes" << std::endl; } } // Loop through all records while(esm.hasMoreRecs()) { const ESM::NAME n = esm.getRecName(); uint32_t flags; esm.getRecHeader(flags); EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); if (record == nullptr) { if (skipped.count(n.intval) == 0) { std::cout << "Skipping " << n.toString() << " records." << std::endl; skipped.emplace(n.intval); } 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::vector::iterator match; match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == 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.toString() << " '" << record->getId() << "'\n"; record->print(); } if (record->getType().intval == ESM::REC_CELL && loadCells && interested) { loadCell(record->cast()->get(), esm, info); } if (save) { info.data.mRecords.push_back(record); } else { delete record; } ++info.data.mRecordStats[n.intval]; } } catch(std::exception &e) { std::cout << "\nERROR:\n\n " << e.what() << std::endl; for (const EsmTool::RecordBase* record : info.data.mRecords) delete record; info.data.mRecords.clear(); return 1; } return 0; } #include int clone(Arguments& info) { if (info.outname.empty()) { std::cout << "You need to specify an output name" << std::endl; return 1; } if (load(info) != 0) { std::cout << "Failed to load, aborting." << std::endl; return 1; } size_t recordCount = info.data.mRecords.size(); int digitCount = 1; // For a nicer output if (recordCount > 0) digitCount = (int)std::log10(recordCount) + 1; std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl; int i = 0; for (std::pair stat : info.data.mRecordStats) { ESM::NAME name; name.intval = stat.first; int amount = stat.second; std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; if (++i % 3 == 0) std::cout << std::endl; } if (i % 3 != 0) std::cout << std::endl; std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl; ESM::ESMWriter& esm = info.writer; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); esm.setAuthor(info.data.author); esm.setDescription(info.data.description); esm.setVersion(info.data.version); esm.setRecordCount (recordCount); for (const ESM::Header::MasterData &master : info.data.masters) esm.addMaster(master.name, master.size); std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary); esm.save(save); int saved = 0; for (EsmTool::RecordBase* record : info.data.mRecords) { if (i <= 0) break; const ESM::NAME& typeName = record->getType(); esm.startRecord(typeName.toString(), record->getFlags()); record->save(esm); if (typeName.intval == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { for (std::pair &ref : info.data.mCellRefs[ptr]) ref.first.save(esm, ref.second); } } esm.endRecord(typeName.toString()); 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(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.raw_given = false; fileTwo.raw_given = false; fileOne.mode = "clone"; fileTwo.mode = "clone"; fileOne.encoding = info.encoding; fileTwo.encoding = info.encoding; fileOne.filename = info.filename; fileTwo.filename = info.outname; if (load(fileOne) != 0) { std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl; return 1; } if (load(fileTwo) != 0) { std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl; return 1; } if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size()) { std::cout << "Not equal, different amount of records." << std::endl; return 1; } return 0; } openmw-openmw-0.47.0/apps/esmtool/labels.cpp000066400000000000000000000642761413061077700210320ustar00rootroot00000000000000#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; } openmw-openmw-0.47.0/apps/esmtool/labels.hpp000066400000000000000000000044051413061077700210230ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_LABELS_H #define OPENMW_ESMTOOL_LABELS_H #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); // Missing flags functions: // aiServicesFlags, possibly more #endif openmw-openmw-0.47.0/apps/esmtool/record.cpp000066400000000000000000001476171413061077700210470ustar00rootroot00000000000000#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 << " Travel Unknown: " << p.mTravel.mUnk << 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 << " Unknown: " << p.mTarget.mUnk << std::endl; } else if (p.mType == ESM::AI_Activate) { std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; std::cout << " Activate Unknown: " << p.mActivate.mUnk << 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 { RecordBase * RecordBase::create(const ESM::NAME type) { RecordBase *record = nullptr; switch (type.intval) { case ESM::REC_ACTI: { record = new EsmTool::Record; break; } case ESM::REC_ALCH: { record = new EsmTool::Record; break; } case ESM::REC_APPA: { record = new EsmTool::Record; break; } case ESM::REC_ARMO: { record = new EsmTool::Record; break; } case ESM::REC_BODY: { record = new EsmTool::Record; break; } case ESM::REC_BOOK: { record = new EsmTool::Record; break; } case ESM::REC_BSGN: { record = new EsmTool::Record; break; } case ESM::REC_CELL: { record = new EsmTool::Record; break; } case ESM::REC_CLAS: { record = new EsmTool::Record; break; } case ESM::REC_CLOT: { record = new EsmTool::Record; break; } case ESM::REC_CONT: { record = new EsmTool::Record; break; } case ESM::REC_CREA: { record = new EsmTool::Record; break; } case ESM::REC_DIAL: { record = new EsmTool::Record; break; } case ESM::REC_DOOR: { record = new EsmTool::Record; break; } case ESM::REC_ENCH: { record = new EsmTool::Record; break; } case ESM::REC_FACT: { record = new EsmTool::Record; break; } case ESM::REC_GLOB: { record = new EsmTool::Record; break; } case ESM::REC_GMST: { record = new EsmTool::Record; break; } case ESM::REC_INFO: { record = new EsmTool::Record; break; } case ESM::REC_INGR: { record = new EsmTool::Record; break; } case ESM::REC_LAND: { record = new EsmTool::Record; break; } case ESM::REC_LEVI: { record = new EsmTool::Record; break; } case ESM::REC_LEVC: { record = new EsmTool::Record; break; } case ESM::REC_LIGH: { record = new EsmTool::Record; break; } case ESM::REC_LOCK: { record = new EsmTool::Record; break; } case ESM::REC_LTEX: { record = new EsmTool::Record; break; } case ESM::REC_MISC: { record = new EsmTool::Record; break; } case ESM::REC_MGEF: { record = new EsmTool::Record; break; } case ESM::REC_NPC_: { record = new EsmTool::Record; break; } case ESM::REC_PGRD: { record = new EsmTool::Record; break; } case ESM::REC_PROB: { record = new EsmTool::Record; break; } case ESM::REC_RACE: { record = new EsmTool::Record; break; } case ESM::REC_REGN: { record = new EsmTool::Record; break; } case ESM::REC_REPA: { record = new EsmTool::Record; break; } case ESM::REC_SCPT: { record = new EsmTool::Record; break; } case ESM::REC_SKIL: { record = new EsmTool::Record; break; } case ESM::REC_SNDG: { record = new EsmTool::Record; break; } case ESM::REC_SOUN: { record = new EsmTool::Record; break; } case ESM::REC_SPEL: { record = new EsmTool::Record; break; } case ESM::REC_STAT: { record = new EsmTool::Record; break; } case ESM::REC_WEAP: { record = new EsmTool::Record; break; } case ESM::REC_SSCR: { record = new EsmTool::Record; break; } default: record = nullptr; } 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 << " UnknownA: " << (int)mData.mData.mA << std::endl; std::cout << " UnknownB: " << (int)mData.mData.mB << 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.47.0/apps/esmtool/record.hpp000066400000000000000000000103201413061077700210300ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_RECORD_H #define OPENMW_ESMTOOL_RECORD_H #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 RecordBase *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.47.0/apps/essimporter/000077500000000000000000000000001413061077700177375ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/essimporter/CMakeLists.txt000066400000000000000000000017471413061077700225100ustar00rootroot00000000000000set(ESSIMPORTER_FILES main.cpp importer.cpp importplayer.cpp importnpcc.cpp importcrec.cpp importcellref.cpp importacdt.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) openmw-openmw-0.47.0/apps/essimporter/convertacdt.cpp000066400000000000000000000117421413061077700227640ustar00rootroot00000000000000#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 = acdt.mDynamic[i][1]; cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0]; } for (int i=0; i<8; ++i) { cStats.mAttributes[i].mBase = static_cast(acdt.mAttributes[i][1]); cStats.mAttributes[i].mMod = static_cast(acdt.mAttributes[i][0]); cStats.mAttributes[i].mCurrent = static_cast(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.47.0/apps/essimporter/convertacdt.hpp000066400000000000000000000013131413061077700227620ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertcntc.cpp000066400000000000000000000003431413061077700227730ustar00rootroot00000000000000#include "convertcntc.hpp" #include "convertinventory.hpp" namespace ESSImport { void convertCNTC(const CNTC &cntc, ESM::ContainerState &state) { convertInventory(cntc.mInventory, state.mInventory); } } openmw-openmw-0.47.0/apps/essimporter/convertcntc.hpp000066400000000000000000000003761413061077700230060ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertcrec.cpp000066400000000000000000000003421413061077700227570ustar00rootroot00000000000000#include "convertcrec.hpp" #include "convertinventory.hpp" namespace ESSImport { void convertCREC(const CREC &crec, ESM::CreatureState &state) { convertInventory(crec.mInventory, state.mInventory); } } openmw-openmw-0.47.0/apps/essimporter/convertcrec.hpp000066400000000000000000000003741413061077700227710ustar00rootroot00000000000000#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.47.0/apps/essimporter/converter.cpp000066400000000000000000000455001413061077700224560ustar00rootroot00000000000000#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.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); if (cellref.mHasANIS) convertANIS(cellref.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; 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.isNextSub("FRMR")) { CellRef ref; ref.load (esm); cellrefs.push_back(ref); } while (esm.isNextSub("MPCD")) { float notepos[3]; esm.getHT(notepos, 3*sizeof(float)); // 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.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; splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID); std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); std::map, NPCC>::const_iterator 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.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); if (cellref.mHasACSC) convertACSC(cellref.mACSC, objstate.mCreatureStats); convertNpcData(cellref, 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; } std::map, CNTC>::const_iterator 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; } std::map, CREC>::const_iterator 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.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); if (cellref.mHasACSC) convertACSC(cellref.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 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.47.0/apps/essimporter/converter.hpp000066400000000000000000000431401413061077700224610ustar00rootroot00000000000000#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; ESM::SpellState::SpellParams empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this for (const auto & spell : npc.mSpells.mList) mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty; // 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 { REFR refr; refr.load(esm); assert(refr.mRefID == "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 (std::map::const_iterator 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 (std::map::const_iterator 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 (std::map >::const_iterator 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 (std::map::const_iterator 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 { std::stringstream error; error << "Invalid weather ID:" << weatherID << std::endl; throw std::runtime_error(error.str()); } } 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.47.0/apps/essimporter/convertinventory.cpp000066400000000000000000000022571413061077700241070ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertinventory.hpp000066400000000000000000000004351413061077700241100ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertnpcc.cpp000066400000000000000000000005471413061077700227750ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertnpcc.hpp000066400000000000000000000003661413061077700230010ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertplayer.cpp000066400000000000000000000076111413061077700233450ustar00rootroot00000000000000#include "convertplayer.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) { 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.47.0/apps/essimporter/convertplayer.hpp000066400000000000000000000006621413061077700233510ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertscpt.cpp000066400000000000000000000006731413061077700230230ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertscpt.hpp000066400000000000000000000003641413061077700230250ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertscri.cpp000066400000000000000000000015341413061077700230070ustar00rootroot00000000000000#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.47.0/apps/essimporter/convertscri.hpp000066400000000000000000000004341413061077700230120ustar00rootroot00000000000000#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.47.0/apps/essimporter/importacdt.cpp000066400000000000000000000074201413061077700226140ustar00rootroot00000000000000#include "importacdt.hpp" #include #include namespace ESSImport { void ActorData::load(ESM::ESMReader &esm) { 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); mHasACDT = false; if (esm.isNextSub("ACDT")) { mHasACDT = true; esm.getHT(mACDT); } mHasACSC = false; if (esm.isNextSub("ACSC")) { mHasACSC = true; esm.getHT(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")) mSelectedEnchantItem = esm.getHString(); else 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(mSkills, 27*2*sizeof(int)); if (esm.isNextSub("CRED")) // creature only esm.getHExact(mCombatStats, 3*2*sizeof(int)); mSCRI.load(esm); if (esm.isNextSub("ND3D")) esm.skipHSub(); mHasANIS = false; if (esm.isNextSub("ANIS")) { mHasANIS = true; esm.getHT(mANIS); } } } openmw-openmw-0.47.0/apps/essimporter/importacdt.hpp000066400000000000000000000044551413061077700226260ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_ACDT_H #define OPENMW_ESSIMPORT_ACDT_H #include #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 : public ESM::CellRef { 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 virtual void load(ESM::ESMReader& esm); virtual ~ActorData() = default; }; } #endif openmw-openmw-0.47.0/apps/essimporter/importcellref.cpp000066400000000000000000000032011413061077700233060ustar00rootroot00000000000000#include "importcellref.hpp" #include namespace ESSImport { void CellRef::load(ESM::ESMReader &esm) { blank(); // (FRMR subrecord name is already read by the loop in ConvertCell) esm.getHT(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"); ActorData::load(esm); 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.getHNOT(mPos, "DATA", 24); esm.getHNOT(mPos, "DATA", 24); 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.47.0/apps/essimporter/importcellref.hpp000066400000000000000000000007431413061077700233230ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CELLREF_H #define OPENMW_ESSIMPORT_CELLREF_H #include #include #include "importacdt.hpp" namespace ESM { class ESMReader; } namespace ESSImport { struct CellRef : public ActorData { std::string mIndexedRefId; std::string mScript; bool mEnabled; bool mDeleted; void load(ESM::ESMReader& esm) override; virtual ~CellRef() = default; }; } #endif openmw-openmw-0.47.0/apps/essimporter/importcntc.cpp000066400000000000000000000003521413061077700226250ustar00rootroot00000000000000#include "importcntc.hpp" #include namespace ESSImport { void CNTC::load(ESM::ESMReader &esm) { mIndex = 0; esm.getHNT(mIndex, "INDX"); mInventory.load(esm); } } openmw-openmw-0.47.0/apps/essimporter/importcntc.hpp000066400000000000000000000005221413061077700226310ustar00rootroot00000000000000#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.47.0/apps/essimporter/importcrec.cpp000066400000000000000000000011131413061077700226060ustar00rootroot00000000000000#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.47.0/apps/essimporter/importcrec.hpp000066400000000000000000000006151413061077700226210ustar00rootroot00000000000000#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.47.0/apps/essimporter/importdial.cpp000066400000000000000000000011171413061077700226070ustar00rootroot00000000000000#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.47.0/apps/essimporter/importdial.hpp000066400000000000000000000004011413061077700226070ustar00rootroot00000000000000#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.47.0/apps/essimporter/importer.cpp000066400000000000000000000417551413061077700223200ustar00rootroot00000000000000#include "importer.hpp" #include #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<'R','E','F','R'>::value; const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value; const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value; const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value; const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value; std::map > converters; converters[ESM::REC_GLOB] = std::shared_ptr(new ConvertGlobal()); converters[ESM::REC_BOOK] = std::shared_ptr(new ConvertBook()); converters[ESM::REC_NPC_] = std::shared_ptr(new ConvertNPC()); converters[ESM::REC_CREA] = std::shared_ptr(new ConvertCREA()); converters[ESM::REC_NPCC] = std::shared_ptr(new ConvertNPCC()); converters[ESM::REC_CREC] = std::shared_ptr(new ConvertCREC()); converters[recREFR ] = std::shared_ptr(new ConvertREFR()); converters[recPCDT ] = std::shared_ptr(new ConvertPCDT()); converters[recFMAP ] = std::shared_ptr(new ConvertFMAP()); converters[recKLST ] = std::shared_ptr(new ConvertKLST()); converters[recSTLN ] = std::shared_ptr(new ConvertSTLN()); converters[recGAME ] = std::shared_ptr(new ConvertGAME()); converters[ESM::REC_CELL] = std::shared_ptr(new ConvertCell()); converters[ESM::REC_ALCH] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_CLAS] = std::shared_ptr(new ConvertClass()); converters[ESM::REC_SPEL] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_ARMO] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_CLOT] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_ENCH] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_LEVC] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_LEVI] = std::shared_ptr(new DefaultConverter()); converters[ESM::REC_CNTC] = std::shared_ptr(new ConvertCNTC()); converters[ESM::REC_FACT] = std::shared_ptr(new ConvertFACT()); converters[ESM::REC_INFO] = std::shared_ptr(new ConvertINFO()); converters[ESM::REC_DIAL] = std::shared_ptr(new ConvertDIAL()); converters[ESM::REC_QUES] = std::shared_ptr(new ConvertQUES()); converters[recJOUR ] = std::shared_ptr(new ConvertJOUR()); converters[ESM::REC_SCPT] = std::shared_ptr(new ConvertSCPT()); converters[ESM::REC_PROJ] = std::shared_ptr(new ConvertPROJ()); converters[recSPLM] = std::shared_ptr(new ConvertSPLM()); // 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.intval); if (it != converters.end()) { it->second->read(esm); } else { if (unknownRecords.insert(n.intval).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.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 (std::map >::const_iterator 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 (std::map >::const_iterator 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 (std::map >::const_iterator 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.47.0/apps/essimporter/importer.hpp000066400000000000000000000006541413061077700223160ustar00rootroot00000000000000#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.47.0/apps/essimporter/importercontext.cpp000066400000000000000000000000001413061077700236770ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/essimporter/importercontext.hpp000066400000000000000000000053121413061077700237170ustar00rootroot00000000000000#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.47.0/apps/essimporter/importgame.cpp000066400000000000000000000010741413061077700226110ustar00rootroot00000000000000#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.47.0/apps/essimporter/importgame.hpp000066400000000000000000000013401413061077700226120ustar00rootroot00000000000000#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.47.0/apps/essimporter/importinfo.cpp000066400000000000000000000003531413061077700226320ustar00rootroot00000000000000#include "importinfo.hpp" #include namespace ESSImport { void INFO::load(ESM::ESMReader &esm) { mInfo = esm.getHNString("INAM"); mActorRefId = esm.getHNString("ACDT"); } } openmw-openmw-0.47.0/apps/essimporter/importinfo.hpp000066400000000000000000000004541413061077700226410ustar00rootroot00000000000000#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.47.0/apps/essimporter/importinventory.cpp000066400000000000000000000043651413061077700237430ustar00rootroot00000000000000#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.47.0/apps/essimporter/importinventory.hpp000066400000000000000000000012641413061077700237430ustar00rootroot00000000000000#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.47.0/apps/essimporter/importjour.cpp000066400000000000000000000002741413061077700226600ustar00rootroot00000000000000#include "importjour.hpp" #include namespace ESSImport { void JOUR::load(ESM::ESMReader &esm) { mText = esm.getHNString("NAME"); } } openmw-openmw-0.47.0/apps/essimporter/importjour.hpp000066400000000000000000000005021413061077700226570ustar00rootroot00000000000000#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.47.0/apps/essimporter/importklst.cpp000066400000000000000000000006601413061077700226550ustar00rootroot00000000000000#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.47.0/apps/essimporter/importklst.hpp000066400000000000000000000005621413061077700226630ustar00rootroot00000000000000#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.47.0/apps/essimporter/importnpcc.cpp000066400000000000000000000006211413061077700226200ustar00rootroot00000000000000#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.47.0/apps/essimporter/importnpcc.hpp000066400000000000000000000011461413061077700226300ustar00rootroot00000000000000#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.47.0/apps/essimporter/importplayer.cpp000066400000000000000000000047211413061077700231760ustar00rootroot00000000000000#include "importplayer.hpp" #include namespace ESSImport { void REFR::load(ESM::ESMReader &esm) { esm.getHNT(mRefNum.mIndex, "FRMR"); mRefID = esm.getHNString("NAME"); mActorData.load(esm); esm.getHNOT(mPos, "DATA", 24); } 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.47.0/apps/essimporter/importplayer.hpp000066400000000000000000000062261413061077700232050ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_PLAYER_H #define OPENMW_ESSIMPORT_PLAYER_H #include #include #include #include #include #include "importacdt.hpp" namespace ESM { class ESMReader; } namespace ESSImport { /// Player-agnostic player data struct REFR { ActorData mActorData; std::string mRefID; ESM::Position mPos; ESM::RefNum mRefNum; void load(ESM::ESMReader& esm); }; /// 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) }; 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[40]; 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.47.0/apps/essimporter/importproj.cpp000066400000000000000000000004201413061077700226440ustar00rootroot00000000000000#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.47.0/apps/essimporter/importproj.h000066400000000000000000000017171413061077700223230ustar00rootroot00000000000000#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.47.0/apps/essimporter/importques.cpp000066400000000000000000000003501413061077700226510ustar00rootroot00000000000000#include "importques.hpp" #include namespace ESSImport { void QUES::load(ESM::ESMReader &esm) { while (esm.isNextSub("DATA")) mInfo.push_back(esm.getHString()); } } openmw-openmw-0.47.0/apps/essimporter/importques.hpp000066400000000000000000000011151413061077700226560ustar00rootroot00000000000000#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.47.0/apps/essimporter/importscpt.cpp000066400000000000000000000006101413061077700226440ustar00rootroot00000000000000#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.47.0/apps/essimporter/importscpt.hpp000066400000000000000000000011201413061077700226460ustar00rootroot00000000000000#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.47.0/apps/essimporter/importscri.cpp000066400000000000000000000024471413061077700226450ustar00rootroot00000000000000#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.47.0/apps/essimporter/importsplm.cpp000066400000000000000000000020271413061077700226520ustar00rootroot00000000000000#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.47.0/apps/essimporter/importsplm.h000066400000000000000000000030271413061077700223200ustar00rootroot00000000000000#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::FIXED_STRING<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.47.0/apps/essimporter/main.cpp000066400000000000000000000045551413061077700214000ustar00rootroot00000000000000#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); 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.47.0/apps/launcher/000077500000000000000000000000001413061077700171645ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/launcher/CMakeLists.txt000066400000000000000000000045741413061077700217360ustar00rootroot00000000000000set(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_HEADER_MOC datafilespage.hpp graphicspage.hpp maindialog.hpp playpage.hpp textslotmsgbox.hpp settingspage.hpp advancedpage.hpp utils/cellnameloader.hpp utils/textinputdialog.hpp utils/profilescombobox.hpp utils/lineedit.hpp utils/openalutil.hpp ) 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 ) 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_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) 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 ) target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-launcher gcov) endif() openmw-openmw-0.47.0/apps/launcher/advancedpage.cpp000066400000000000000000000510151413061077700222740ustar00rootroot00000000000000#include "advancedpage.hpp" #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 char * name : Launcher::enumerateOpenALDevices()) { audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); } for(const char * name : Launcher::enumerateOpenALDevicesHrtf()) { hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(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 std::round((unitRadius + 1024) / CellSizeInUnits); } double convertToUnits(double CellGridRadius) { return CellSizeInUnits * CellGridRadius - 1024; } } 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(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"); } // 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(radialFogCheckBox, "radial fog", "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")); } // 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); } } } // Camera { loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); connect(viewOverShoulderCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotViewOverShoulderToggled(bool))); viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); loadSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera"); loadSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera"); loadSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); loadSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); defaultShoulderComboBox->setCurrentIndex( Settings::Manager::getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1); } // 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(graphicHerbalismCheckBox, "graphic herbalism", "Game"); scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "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"); maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("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)); } // 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(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"); int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game")) Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); int numPhysicsThreads = physicsThreadsSpinBox->value(); if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics")) Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads); } // 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", "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"); double viewingDistance = viewingDistanceComboBox->value(); if (viewingDistance != convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))) { Settings::Manager::setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); } double objectPagingMinSize = objectPagingMinSizeComboBox->value(); if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); } // Audio { int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex(); if (audioDeviceIndex != 0) { Settings::Manager::setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData()); } else { 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(); if (selectedHRTFProfileIndex != 0) { Settings::Manager::setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData()); } else { Settings::Manager::setString("hrtf", "Sound", ""); } } // Camera { saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); saveSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera"); saveSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera"); saveSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); saveSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); osg::Vec2f shoulderOffset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); if (defaultShoulderComboBox->currentIndex() != (shoulderOffset.x() >= 0 ? 0 : 1)) { if (defaultShoulderComboBox->currentIndex() == 0) shoulderOffset.x() = std::abs(shoulderOffset.x()); else shoulderOffset.x() = -std::abs(shoulderOffset.x()); Settings::Manager::setVector2("view over shoulder offset", "Camera", shoulderOffset); } } // 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"); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); float uiScalingFactor = scalingSpinBox->value(); if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor); } // 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"); int maximumQuicksaves = maximumQuicksavesComboBox->value(); if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves")) { Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves); } // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General")) Settings::Manager::setString("screenshot format", "General", screenshotFormatString); } // 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::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::slotViewOverShoulderToggled(bool checked) { viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); } openmw-openmw-0.47.0/apps/launcher/advancedpage.hpp000066400000000000000000000026461413061077700223070ustar00rootroot00000000000000#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 slotViewOverShoulderToggled(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); void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); }; } #endif openmw-openmw-0.47.0/apps/launcher/datafilespage.cpp000066400000000000000000000314261413061077700224670ustar00rootroot00000000000000#include "datafilespage.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) { ui.setupUi (this); setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); 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))); 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() { ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); 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"); refreshButton->setToolTip("Refresh Data Files"); //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())); } bool Launcher::DataFilesPage::loadSettings() { 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) { QStringList paths = mGameSettings.getDataDirs(); mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) paths.insert(0, mDataLocal); mSelector->clearFiles(); for (const QString &path : paths) mSelector->addFiles(path); PathIterator pathIterator(paths); 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) { QString profileName = profile; if (profileName.isEmpty()) profileName = ui.profilesComboBox->currentText(); //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, fileNames); mGameSettings.setContentList(fileNames); } QStringList Launcher::DataFilesPage::selectedFilePaths() { //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()); } else { slotRefreshButtonClicked(); } } 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)); populateFileViews(current); 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, 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); } 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. 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); } openmw-openmw-0.47.0/apps/launcher/datafilespage.hpp000066400000000000000000000106341413061077700224720ustar00rootroot00000000000000#ifndef DATAFILESPAGE_H #define DATAFILESPAGE_H #include "ui_datafilespage.h" #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 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, QWidget *parent = nullptr); QAbstractItemModel* profilesModel() const; int profilesIndex() const; //void writeConfig(QString profile = QString()); void saveSettings(const QString &profile = ""); bool loadSettings(); /** * Returns the file paths of all selected content files * @return the file paths of all selected content files */ QStringList selectedFilePaths(); 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 on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); public: /// Content List that is always present const static char *mDefaultContentListName; private: TextInputDialog *mNewProfileDialog; TextInputDialog *mCloneProfileDialog; Files::ConfigurationManager &mCfgMgr; Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; QString mPreviousProfile; QStringList previousSelectedFiles; QString mDataLocal; 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 (); 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.47.0/apps/launcher/graphicspage.cpp000066400000000000000000000362761413061077700223430ustar00rootroot00000000000000#include "graphicspage.hpp" #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 #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(fullScreenCheckBox, SIGNAL(stateChanged(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); if (Settings::Manager::getBool("fullscreen", "Video")) fullScreenCheckBox->setCheckState(Qt::Checked); 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); bool cFullScreen = fullScreenCheckBox->checkState(); if (cFullScreen != Settings::Manager::getBool("fullscreen", "Video")) Settings::Manager::setBool("fullscreen", "Video", cFullScreen); 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"}; Settings::Manager::setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]); // 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 state) { if (state == Qt::Checked) { 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.47.0/apps/launcher/graphicspage.hpp000066400000000000000000000017061413061077700223360ustar00rootroot00000000000000#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.47.0/apps/launcher/main.cpp000066400000000000000000000025611413061077700206200ustar00rootroot00000000000000#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 main(int argc, char *argv[]) { 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; } } openmw-openmw-0.47.0/apps/launcher/maindialog.cpp000066400000000000000000000526101413061077700220000ustar00rootroot00000000000000#include "maindialog.hpp" #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); QPushButton *helpButton = new QPushButton(tr("Help")); QPushButton *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"); QListWidgetItem *playButton = new QListWidgetItem(iconWidget); playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setText(tr("Play")); playButton->setTextAlignment(Qt::AlignCenter); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); QListWidgetItem *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); QListWidgetItem *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); QListWidgetItem *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); QListWidgetItem *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; 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()); // 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)) { 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")); mGameSettings.readUserFile(stream); file.close(); } // Now the rest - priority: user > local > global QStringList paths; paths.append(globalPath + QString("openmw.cfg")); paths.append(localPath + QString("openmw.cfg")); paths.append(userPath + QString("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)) { 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")); mGameSettings.readFile(stream); file.close(); } } 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() { // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely // remain consistent, and possibly be merged into a shared component. At the very least // the filenames should be in the CfgMgr component. // Ensure to clear previous settings in case we had already loaded settings. mEngineSettings.clear(); // Create the settings manager and load default settings file const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); std::string defaultPath; // Prefer the defaults.bin in the current directory. if (boost::filesystem::exists(localDefault)) defaultPath = localDefault; else if (boost::filesystem::exists(globalDefault)) defaultPath = globalDefault; // Something's very wrong if we can't find the file at all. else { cfgError(tr("Error reading OpenMW configuration file"), tr("
Could not find defaults.bin

\ The problem may be due to an incomplete installation of OpenMW.
\ Reinstalling OpenMW may resolve the problem.")); return false; } // Load the default settings, report any parsing errors. try { mEngineSettings.loadDefault(defaultPath); } catch (std::exception& e) { std::string msg = std::string("
Error reading defaults.bin

") + e.what(); cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); return false; } // Load user settings if they exist const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); // User settings are not required to exist, so if they don't we're done. if (!boost::filesystem::exists(userPath)) return true; try { mEngineSettings.loadUser(userPath); } catch (std::exception& e) { std::string msg = std::string("
Error reading settings.cfg

") + e.what(); cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); return false; } return true; } 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 { mEngineSettings.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.47.0/apps/launcher/maindialog.hpp000066400000000000000000000046771413061077700220170ustar00rootroot00000000000000#ifndef MAINDIALOG_H #define MAINDIALOG_H #ifndef Q_MOC_RUN #include #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(); 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; Settings::Manager mEngineSettings; Config::LauncherSettings mLauncherSettings; }; } #endif openmw-openmw-0.47.0/apps/launcher/playpage.cpp000066400000000000000000000012541413061077700214740ustar00rootroot00000000000000#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.47.0/apps/launcher/playpage.hpp000066400000000000000000000010741413061077700215010ustar00rootroot00000000000000#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.47.0/apps/launcher/sdlinit.cpp000066400000000000000000000010341413061077700213340ustar00rootroot00000000000000#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.47.0/apps/launcher/sdlinit.hpp000066400000000000000000000001161413061077700213410ustar00rootroot00000000000000#ifndef SDLINIT_H #define SDLINIT_H bool initSDL(); void quitSDL(); #endif openmw-openmw-0.47.0/apps/launcher/settingspage.cpp000066400000000000000000000177111413061077700223740ustar00rootroot00000000000000#include "settingspage.hpp" #include #include #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")); 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.47.0/apps/launcher/settingspage.hpp000066400000000000000000000030541413061077700223740ustar00rootroot00000000000000#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(); 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.47.0/apps/launcher/textslotmsgbox.cpp000066400000000000000000000001721413061077700227760ustar00rootroot00000000000000#include "textslotmsgbox.hpp" void Launcher::TextSlotMsgBox::setTextSlot(const QString& string) { setText(string); } openmw-openmw-0.47.0/apps/launcher/textslotmsgbox.hpp000066400000000000000000000003761413061077700230110ustar00rootroot00000000000000#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.47.0/apps/launcher/utils/000077500000000000000000000000001413061077700203245ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/launcher/utils/cellnameloader.cpp000066400000000000000000000024071413061077700240020ustar00rootroot00000000000000#include "cellnameloader.hpp" #include #include QSet CellNameLoader::getCellNames(QStringList &contentPaths) { QSet cellNames; ESM::ESMReader esmReader; // Loop through all content files for (auto &contentPath : contentPaths) { 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.intval == 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.47.0/apps/launcher/utils/cellnameloader.hpp000066400000000000000000000020271413061077700240050ustar00rootroot00000000000000#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.47.0/apps/launcher/utils/lineedit.cpp000066400000000000000000000020201413061077700226170ustar00rootroot00000000000000#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.47.0/apps/launcher/utils/lineedit.hpp000066400000000000000000000014651413061077700226400ustar00rootroot00000000000000/**************************************************************************** ** ** 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.47.0/apps/launcher/utils/openalutil.cpp000066400000000000000000000031701413061077700232050ustar00rootroot00000000000000#include #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.47.0/apps/launcher/utils/openalutil.hpp000066400000000000000000000002351413061077700232110ustar00rootroot00000000000000#include namespace Launcher { std::vector enumerateOpenALDevices(); std::vector enumerateOpenALDevicesHrtf(); }openmw-openmw-0.47.0/apps/launcher/utils/profilescombobox.cpp000066400000000000000000000050561413061077700244120ustar00rootroot00000000000000#include #include #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); ComboBoxLineEdit *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.47.0/apps/launcher/utils/profilescombobox.hpp000066400000000000000000000021151413061077700244100ustar00rootroot00000000000000#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.47.0/apps/launcher/utils/textinputdialog.cpp000066400000000000000000000036341413061077700242620ustar00rootroot00000000000000#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); QLabel *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); QVBoxLayout *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.47.0/apps/launcher/utils/textinputdialog.hpp000066400000000000000000000011541413061077700242620ustar00rootroot00000000000000#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 (); inline LineEdit *lineEdit() { return mLineEdit; } void setOkButtonEnabled(bool enabled); int exec() override; private: QDialogButtonBox *mButtonBox; LineEdit *mLineEdit; }; } #endif // TEXTINPUTDIALOG_HPP openmw-openmw-0.47.0/apps/mwiniimporter/000077500000000000000000000000001413061077700202705ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/mwiniimporter/CMakeLists.txt000066400000000000000000000013011413061077700230230ustar00rootroot00000000000000set(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() openmw-openmw-0.47.0/apps/mwiniimporter/importer.cpp000066400000000000000000001056231413061077700226440ustar00rootroot00000000000000#include "importer.hpp" #include #include #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)) { line = encoder.getUtf8(line); // unify Unix-style and Windows file ending if (!(line.empty()) && (line[line.length()-1]) == '\r') { line = line.substr(0, line.length()-1); } if(line.empty()) { continue; } if(line[0] == '[') { int pos = static_cast(line.find(']')); if(pos < 2) { std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; continue; } section = line.substr(1, line.find(']')-1); continue; } int comment_pos = static_cast(line.find(';')); if(comment_pos > 0) { line = line.substr(0,comment_pos); } int pos = static_cast(line.find('=')); if(pos < 1) { continue; } std::string key(section + ":" + line.substr(0,pos)); std::string value(line.substr(pos+1)); if(value.empty()) { std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl; continue; } if(map.find(key) == map.end()) { map.insert( std::make_pair (key, std::vector() ) ); } map[key].push_back(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 multistrmap::const_iterator 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 multistrmap::const_iterator 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(std::vector::const_iterator 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"); multistrmap::const_iterator 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(std::vector::const_iterator 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.47.0/apps/mwiniimporter/importer.hpp000066400000000000000000000040451413061077700226450ustar00rootroot00000000000000#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.47.0/apps/mwiniimporter/main.cpp000066400000000000000000000110111413061077700217120ustar00rootroot00000000000000#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") ("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); 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.47.0/apps/niftest/000077500000000000000000000000001413061077700170375ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/niftest/CMakeLists.txt000066400000000000000000000005161413061077700216010ustar00rootroot00000000000000set(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() openmw-openmw-0.47.0/apps/niftest/niftest.cpp000066400000000000000000000125111413061077700212170ustar00rootroot00000000000000///Program to test .nif files both on the FileSystem and in BSA archives. #include #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); //Convert strings to lower case for comparison std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); if(extension == extensionToFind) return true; else return false; } ///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 Takes ownership! /// \note Can not read a bsa file inside of a bsa file. void readVFS(VFS::Archive* anArchive,std::string archivePath = "") { VFS::Manager myManager(true); myManager.addArchive(anArchive); myManager.buildIndex(); std::map files=myManager.getIndex(); for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) { std::string name = it->first; 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(new VFS::BsaArchive(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(std::vector::const_iterator 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.c_str()),name); } else if(isBSA(name)) { // std::cout << "Reading BSA File: " << name << std::endl; readVFS(new VFS::BsaArchive(name)); } else if(bfs::is_directory(bfs::path(name))) { // std::cout << "Reading All Files in: " << name << std::endl; readVFS(new VFS::FileSystemArchive(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.47.0/apps/opencs/000077500000000000000000000000001413061077700166525ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/CMakeLists.txt000066400000000000000000000225051413061077700214160ustar00rootroot00000000000000set (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_noqt (model/doc stage savingstate savingstages blacklist messages ) opencs_hdrs_noqt (model/doc state ) opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel actoradapter ) opencs_units_noqt (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_noqt (model/world columnimp idcollection collection info subcellcollection ) opencs_units (model/tools tools reportmodel mergeoperation ) opencs_units_noqt (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_noqt (model/tools mergestate ) opencs_units (view/doc viewmanager view operations operation subview startup filedialog newgame filewidget adjusterwidget loader globaldebugprofilemenu runlogsubview sizehint ) opencs_units_noqt (view/doc subviewfactory ) opencs_hdrs_noqt (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 ) opencs_units_noqt (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_noqt (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) opencs_hdrs_noqt (view/render mask ) opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) opencs_units_noqt (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_noqt (model/prefs category ) opencs_units_noqt (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_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) 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_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) set(OPENMW_SHADERS_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 ) if(OSG_STATIC) unset(_osg_plugins_static_files) add_library(openmw_cs_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(openmw_cs_osg_plugins INTERFACE $) add_dependencies(openmw_cs_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(openmw_cs_osg_plugins INTERFACE ${_opts}) target_link_libraries(openmw-cs openmw_cs_osg_plugins) if(OPENMW_USE_SYSTEM_OSG) # OSG plugin pkgconfig files are missing these dependencies. # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 target_link_libraries(openmw freetype jpeg png) endif() endif(OSG_STATIC) 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() openmw-openmw-0.47.0/apps/opencs/editor.cpp000066400000000000000000000324051413061077700206500ustar00rootroot00000000000000#include "editor.hpp" #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) : 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 } std::pair > CS::Editor::readConfig(bool quiet) { 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::EscapePathContainer(), "data")->multitoken()->composing()) ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")) ("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::EscapePath(), "resources")) ("fallback-archive", boost::program_options::value()-> default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") ("script-blacklist", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") ->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"); boost::program_options::notify(variables); mCfgMgr.readConfiguration(variables, desc, false); Fallback::Map::init(variables["fallback"].as().mMap); mEncodingName = variables["encoding"].as().toStdString(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); mDocumentManager.setResourceDir (mResources = variables["resources"].as().mPath); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( variables["script-blacklist"].as().toStdStringVector()); mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as())); } Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) dataLocal.push_back(local); mCfgMgr.processPaths (dataDirs); mCfgMgr.processPaths (dataLocal, true); 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 for (Files::PathContainer::const_reverse_iterator iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) { QString path = QString::fromUtf8 (iter->string().c_str()); mFileDialog.addFiles(path); } return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); } 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.47.0/apps/opencs/editor.hpp000066400000000000000000000056471413061077700206650ustar00rootroot00000000000000#ifndef CS_EDITOR_H #define CS_EDITOR_H #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 { class Editor : public QObject { Q_OBJECT Files::ConfigurationManager mCfgMgr; 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; 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.47.0/apps/opencs/main.cpp000066400000000000000000000033201413061077700203000ustar00rootroot00000000000000#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[]) { #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, "OpenMW-CS"); } openmw-openmw-0.47.0/apps/opencs/model/000077500000000000000000000000001413061077700177525ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/doc/000077500000000000000000000000001413061077700205175ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/doc/blacklist.cpp000066400000000000000000000015041413061077700231730ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/blacklist.hpp000066400000000000000000000010231413061077700231740ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/document.cpp000066400000000000000000000331031413061077700230410ustar00rootroot00000000000000#include "document.hpp" #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.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.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.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) { CSMWorld::Record record; record.mBase = gmst; record.mState = CSMWorld::RecordBase::State_BaseOnly; getData().getGmsts().appendRecord (record); } } void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global) { if (getData().getGlobals().searchId (global.mId)==-1) { CSMWorld::Record record; record.mBase = global; record.mState = CSMWorld::RecordBase::State_BaseOnly; getData().getGlobals().appendRecord (record); } } void CSMDoc::Document::addOptionalMagicEffect (const ESM::MagicEffect& magicEffect) { if (getData().getMagicEffects().searchId (magicEffect.mId)==-1) { CSMWorld::Record record; record.mBase = magicEffect; record.mState = CSMWorld::RecordBase::State_BaseOnly; getData().getMagicEffects().appendRecord (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.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.47.0/apps/opencs/model/doc/document.hpp000066400000000000000000000121311413061077700230440ustar00rootroot00000000000000#ifndef CSM_DOC_DOCUMENT_H #define CSM_DOC_DOCUMENT_H #include #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.47.0/apps/opencs/model/doc/documentmanager.cpp000066400000000000000000000103521413061077700243750ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/documentmanager.hpp000066400000000000000000000077411413061077700244120ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/loader.cpp000066400000000000000000000071501413061077700224740ustar00rootroot00000000000000#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, false); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; emit nextStage (document, path.filename().string(), steps); } else if (iter->second.mFile==size) { int steps = document->getData().startLoading (document->getProjectPath(), 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.47.0/apps/opencs/model/doc/loader.hpp000066400000000000000000000042361413061077700225030ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/messages.cpp000066400000000000000000000024101413061077700230270ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/messages.hpp000066400000000000000000000031271413061077700230420ustar00rootroot00000000000000#ifndef CSM_DOC_MESSAGES_H #define CSM_DOC_MESSAGES_H #include #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.47.0/apps/opencs/model/doc/operation.cpp000066400000000000000000000060511413061077700232250ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/operation.hpp000066400000000000000000000037001413061077700232300ustar00rootroot00000000000000#ifndef CSM_DOC_OPERATION_H #define CSM_DOC_OPERATION_H #include #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.47.0/apps/opencs/model/doc/operationholder.cpp000066400000000000000000000026041413061077700244230ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/operationholder.hpp000066400000000000000000000020351413061077700244260ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/runner.cpp000066400000000000000000000074421413061077700225430ustar00rootroot00000000000000#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"; 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.47.0/apps/opencs/model/doc/runner.hpp000066400000000000000000000040721413061077700225440ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/saving.cpp000066400000000000000000000103171413061077700225140ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/saving.hpp000066400000000000000000000010061413061077700225140ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/savingstages.cpp000066400000000000000000000414301413061077700237230ustar00rootroot00000000000000#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); mState.getWriter().setFormat (ESM::Header::CurrentFormat); } 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 = ""; 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 = ""; 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::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); 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(); 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()) { for (std::deque::const_iterator iter (references->second.begin()); iter!=references->second.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); } } } 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.47.0/apps/opencs/model/doc/savingstages.hpp000066400000000000000000000165301413061077700237330ustar00rootroot00000000000000#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.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; 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.47.0/apps/opencs/model/doc/savingstate.cpp000066400000000000000000000026311413061077700235550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/savingstate.hpp000066400000000000000000000030111413061077700235530ustar00rootroot00000000000000#ifndef CSM_DOC_SAVINGSTATE_H #define CSM_DOC_SAVINGSTATE_H #include #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.47.0/apps/opencs/model/doc/stage.cpp000066400000000000000000000000611413061077700223230ustar00rootroot00000000000000#include "stage.hpp" CSMDoc::Stage::~Stage() {} openmw-openmw-0.47.0/apps/opencs/model/doc/stage.hpp000066400000000000000000000007701413061077700223370ustar00rootroot00000000000000#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.47.0/apps/opencs/model/doc/state.hpp000066400000000000000000000006501413061077700223510ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/000077500000000000000000000000001413061077700212375ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/filter/andnode.cpp000066400000000000000000000006601413061077700233550ustar00rootroot00000000000000#include "andnode.hpp" #include 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.47.0/apps/opencs/model/filter/booleannode.cpp000066400000000000000000000005471413061077700242360ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/booleannode.hpp000066400000000000000000000014121413061077700242330ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/leafnode.cpp000066400000000000000000000002001413061077700235100ustar00rootroot00000000000000#include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const { return std::vector(); } openmw-openmw-0.47.0/apps/opencs/model/filter/leafnode.hpp000066400000000000000000000006771413061077700235370ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/narynode.cpp000066400000000000000000000022731413061077700235660ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/node.cpp000066400000000000000000000001151413061077700226650ustar00rootroot00000000000000#include "node.hpp" CSMFilter::Node::Node() {} CSMFilter::Node::~Node() {} openmw-openmw-0.47.0/apps/opencs/model/filter/node.hpp000066400000000000000000000027101413061077700226750ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/notnode.cpp000066400000000000000000000004411413061077700234100ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/notnode.hpp000066400000000000000000000010011413061077700234060ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/ornode.cpp000066400000000000000000000006521413061077700232340ustar00rootroot00000000000000#include "ornode.hpp" #include 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.47.0/apps/opencs/model/filter/parser.cpp000066400000000000000000000336011413061077700232420ustar00rootroot00000000000000#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 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=='_' || std::isalpha (c) || c==':') return getStringToken(); if (c=='-' || c=='.' || std::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::shared_ptr (new BooleanNode (true)); case Token::Type_Keyword_False: return std::shared_ptr (new BooleanNode (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::shared_ptr (new NotNode (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::shared_ptr (new AndNode (nodes)); case Token::Type_Keyword_Or: return std::shared_ptr (new OrNode (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::shared_ptr (new TextNode (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::shared_ptr (new ValueNode (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.reset (new BooleanNode (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.47.0/apps/opencs/model/filter/parser.hpp000066400000000000000000000025161413061077700232500ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/textnode.cpp000066400000000000000000000042571413061077700236050ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/textnode.hpp000066400000000000000000000020661413061077700236060ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/unarynode.cpp000066400000000000000000000010611413061077700237450ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/unarynode.hpp000066400000000000000000000016041413061077700237550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/valuenode.cpp000066400000000000000000000051001413061077700237210ustar00rootroot00000000000000#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.47.0/apps/opencs/model/filter/valuenode.hpp000066400000000000000000000025321413061077700237340ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/000077500000000000000000000000001413061077700210715ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/prefs/boolsetting.cpp000066400000000000000000000027741413061077700241400ustar00rootroot00000000000000 #include "boolsetting.hpp" #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, bool default_) : Setting (parent, values, 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(getValues().getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked); } } void CSMPrefs::BoolSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); getValues().setBool (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/boolsetting.hpp000066400000000000000000000014351413061077700241360ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/category.cpp000066400000000000000000000020361413061077700234130ustar00rootroot00000000000000 #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.47.0/apps/opencs/model/prefs/category.hpp000066400000000000000000000014251413061077700234210ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/coloursetting.cpp000066400000000000000000000032501413061077700244760ustar00rootroot00000000000000 #include "coloursetting.hpp" #include #include #include #include #include "../../view/widget/coloreditor.hpp" #include "category.hpp" #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, QColor default_) : Setting (parent, values, 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 (getValues().getString(getKey(), getParent()->getKey()))); } } void CSMPrefs::ColourSetting::valueChanged() { CSVWidget::ColorEditor& widget = dynamic_cast (*sender()); { QMutexLocker lock (getMutex()); getValues().setString (getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/coloursetting.hpp000066400000000000000000000015571413061077700245130ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/doublesetting.cpp000066400000000000000000000042251413061077700244500ustar00rootroot00000000000000 #include "doublesetting.hpp" #include #include #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, double default_) : Setting (parent, values, 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(getValues().getFloat(getKey(), getParent()->getKey())); } } void CSMPrefs::DoubleSetting::valueChanged (double value) { { QMutexLocker lock (getMutex()); getValues().setFloat (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/doublesetting.hpp000066400000000000000000000022641413061077700244560ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/enumsetting.cpp000066400000000000000000000061541413061077700241450ustar00rootroot00000000000000 #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, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) : Setting (parent, values, 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 (getValues().getString(getKey(), getParent()->getKey()))); mWidget->setCurrentIndex(index); } } void CSMPrefs::EnumSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); getValues().setString (getKey(), getParent()->getKey(), mValues.mValues.at (value).mValue); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/enumsetting.hpp000066400000000000000000000030431413061077700241440ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/intsetting.cpp000066400000000000000000000036101413061077700237650ustar00rootroot00000000000000 #include "intsetting.hpp" #include #include #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::IntSetting::IntSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, int default_) : Setting (parent, values, 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(getValues().getInt(getKey(), getParent()->getKey())); } } void CSMPrefs::IntSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); getValues().setInt (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/intsetting.hpp000066400000000000000000000020141413061077700237670ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/modifiersetting.cpp000066400000000000000000000105771413061077700250030ustar00rootroot00000000000000#include "modifiersetting.hpp" #include #include #include #include #include #include #include "state.hpp" #include "shortcutmanager.hpp" namespace CSMPrefs { ModifierSetting::ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, values, 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 = getValues().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()); getValues().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.47.0/apps/opencs/model/prefs/modifiersetting.hpp000066400000000000000000000016711413061077700250030ustar00rootroot00000000000000#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, Settings::Manager* values, 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.47.0/apps/opencs/model/prefs/setting.cpp000066400000000000000000000041471413061077700232600ustar00rootroot00000000000000 #include "setting.hpp" #include #include #include "category.hpp" #include "state.hpp" Settings::Manager& CSMPrefs::Setting::getValues() { return *mValues; } QMutex *CSMPrefs::Setting::getMutex() { return mMutex; } CSMPrefs::Setting::Setting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label) : QObject (parent->getState()), mParent (parent), mValues (values), 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 mValues->getInt (mKey, mParent->getKey()); } double CSMPrefs::Setting::toDouble() const { QMutexLocker lock (mMutex); return mValues->getFloat (mKey, mParent->getKey()); } std::string CSMPrefs::Setting::toString() const { QMutexLocker lock (mMutex); return mValues->getString (mKey, mParent->getKey()); } bool CSMPrefs::Setting::isTrue() const { QMutexLocker lock (mMutex); return mValues->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.47.0/apps/opencs/model/prefs/setting.hpp000066400000000000000000000036331413061077700232640ustar00rootroot00000000000000#ifndef CSM_PREFS_SETTING_H #define CSM_PREFS_SETTING_H #include #include #include class QWidget; class QColor; class QMutex; namespace Settings { class Manager; } namespace CSMPrefs { class Category; class Setting : public QObject { Q_OBJECT Category *mParent; Settings::Manager *mValues; QMutex *mMutex; std::string mKey; std::string mLabel; protected: Settings::Manager& getValues(); QMutex *getMutex(); public: Setting (Category *parent, Settings::Manager *values, 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.47.0/apps/opencs/model/prefs/shortcut.cpp000066400000000000000000000122321413061077700234500ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/shortcut.hpp000066400000000000000000000064501413061077700234620ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/shortcuteventhandler.cpp000066400000000000000000000270421413061077700260550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/shortcuteventhandler.hpp000066400000000000000000000034051413061077700260570ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/shortcutmanager.cpp000066400000000000000000001225441413061077700250130ustar00rootroot00000000000000#include "shortcutmanager.hpp" #include #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.47.0/apps/opencs/model/prefs/shortcutmanager.hpp000066400000000000000000000044721413061077700250170ustar00rootroot00000000000000#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.47.0/apps/opencs/model/prefs/shortcutsetting.cpp000066400000000000000000000136151413061077700250540ustar00rootroot00000000000000#include "shortcutsetting.hpp" #include #include #include #include #include #include #include #include "state.hpp" #include "shortcutmanager.hpp" namespace CSMPrefs { ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, values, 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 = getValues().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()); getValues().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.47.0/apps/opencs/model/prefs/shortcutsetting.hpp000066400000000000000000000021101413061077700250450ustar00rootroot00000000000000#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, Settings::Manager* values, 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.47.0/apps/opencs/model/prefs/state.cpp000066400000000000000000001116241413061077700227220ustar00rootroot00000000000000 #include "state.hpp" #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::load() { // default settings file boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; if (boost::filesystem::exists (local)) mSettings.loadDefault (local.string()); else if (boost::filesystem::exists (global)) mSettings.loadDefault (global.string()); else throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); // user settings file boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; if (boost::filesystem::exists (user)) mSettings.loadUser (user.string()); } 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."); 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."); 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"); 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_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); CSMPrefs::IntSetting *setting = new CSMPrefs::IntSetting (&mCurrentCategory->second, &mSettings, &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_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); CSMPrefs::DoubleSetting *setting = new CSMPrefs::DoubleSetting (&mCurrentCategory->second, &mSettings, &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_ = mSettings.getBool (key, mCurrentCategory->second.getKey()); CSMPrefs::BoolSetting *setting = new CSMPrefs::BoolSetting (&mCurrentCategory->second, &mSettings, &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 = mSettings.getString (key, mCurrentCategory->second.getKey()); CSMPrefs::EnumSetting *setting = new CSMPrefs::EnumSetting (&mCurrentCategory->second, &mSettings, &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 (mSettings.getString (key, mCurrentCategory->second.getKey()).c_str())); CSMPrefs::ColourSetting *setting = new CSMPrefs::ColourSetting (&mCurrentCategory->second, &mSettings, &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(mSettings.getString(key, mCurrentCategory->second.getKey()), sequence); getShortcutManager().setSequence(key, sequence); CSMPrefs::ShortcutSetting *setting = new CSMPrefs::ShortcutSetting (&mCurrentCategory->second, &mSettings, &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_ = mSettings.getString (key, mCurrentCategory->second.getKey()); CSMPrefs::StringSetting *setting = new CSMPrefs::StringSetting (&mCurrentCategory->second, &mSettings, &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(mSettings.getString(key, mCurrentCategory->second.getKey()), modifier); getShortcutManager().setModifier(key, modifier); CSMPrefs::ModifierSetting *setting = new CSMPrefs::ModifierSetting (&mCurrentCategory->second, &mSettings, &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, &mSettings, &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, &mSettings, &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 = mSettings.mDefaultSettings.find (fullKey); if (iter==mSettings.mDefaultSettings.end()) mSettings.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; load(); declare(); } CSMPrefs::State::~State() { sThis = nullptr; } void CSMPrefs::State::save() { boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; mSettings.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 = mSettings.mUserSettings.begin(); i != mSettings.mUserSettings.end(); ++i) { // if the category matches if (i->first.first == category) { // mark the setting as changed mSettings.mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); // reset the value to the default i->second = mSettings.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.47.0/apps/opencs/model/prefs/state.hpp000066400000000000000000000063611413061077700227300ustar00rootroot00000000000000#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #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; Settings::Manager mSettings; Collection mCategories; Iterator mCurrentCategory; QMutex mMutex; // not implemented State (const State&); State& operator= (const State&); private: void load(); 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.47.0/apps/opencs/model/prefs/stringsetting.cpp000066400000000000000000000027101413061077700245010ustar00rootroot00000000000000 #include "stringsetting.hpp" #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::StringSetting::StringSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, std::string default_) : Setting (parent, values, 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(getValues().getString(getKey(), getParent()->getKey()))); } } void CSMPrefs::StringSetting::textChanged (const QString& text) { { QMutexLocker lock (getMutex()); getValues().setString (getKey(), getParent()->getKey(), text.toStdString()); } getParent()->getState()->update (*this); } openmw-openmw-0.47.0/apps/opencs/model/prefs/stringsetting.hpp000066400000000000000000000014761413061077700245160ustar00rootroot00000000000000#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, Settings::Manager *values, 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.47.0/apps/opencs/model/tools/000077500000000000000000000000001413061077700211125ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/tools/birthsigncheck.cpp000066400000000000000000000035471413061077700246160ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/birthsigncheck.hpp000066400000000000000000000017121413061077700246130ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/bodypartcheck.cpp000066400000000000000000000040761413061077700244470ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/bodypartcheck.hpp000066400000000000000000000021771413061077700244540ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/classcheck.cpp000066400000000000000000000043031413061077700237210ustar00rootroot00000000000000#include "classcheck.hpp" #include #include #include #include "../prefs/state.hpp" #include "../world/universalid.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.47.0/apps/opencs/model/tools/classcheck.hpp000066400000000000000000000014161413061077700237300ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/enchantmentcheck.cpp000066400000000000000000000072001413061077700251170ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/enchantmentcheck.hpp000066400000000000000000000014501413061077700251250ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/factioncheck.cpp000066400000000000000000000035041413061077700242410ustar00rootroot00000000000000#include "factioncheck.hpp" #include #include #include "../prefs/state.hpp" #include "../world/universalid.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.47.0/apps/opencs/model/tools/factioncheck.hpp000066400000000000000000000014361413061077700242500ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/gmstcheck.cpp000066400000000000000000000115571413061077700235770ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/gmstcheck.hpp000066400000000000000000000015331413061077700235750ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/journalcheck.cpp000066400000000000000000000054361413061077700242760ustar00rootroot00000000000000#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); 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.47.0/apps/opencs/model/tools/journalcheck.hpp000066400000000000000000000016221413061077700242740ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/magiceffectcheck.cpp000066400000000000000000000117321413061077700250550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/magiceffectcheck.hpp000066400000000000000000000031041413061077700250540ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/mandatoryid.cpp000066400000000000000000000013321413061077700241300ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/mandatoryid.hpp000066400000000000000000000017071413061077700241430ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/mergeoperation.cpp000066400000000000000000000070431413061077700246420ustar00rootroot00000000000000 #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.47.0/apps/opencs/model/tools/mergeoperation.hpp000066400000000000000000000016671413061077700246550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/mergestages.cpp000066400000000000000000000133321413061077700241260ustar00rootroot00000000000000 #include "mergestages.hpp" #include #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; CSMWorld::Record newRecord ( CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref); mState.mTarget->getData().getReferences().appendRecord (newRecord); } } 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(record); } } 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 (record); } } 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); CSMWorld::Record newRecord(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &oldRecord.get()); mState.mTarget->getData().getLand().setRecord(stage, newRecord); } } 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.47.0/apps/opencs/model/tools/mergestages.hpp000066400000000000000000000130371413061077700241350ustar00rootroot00000000000000#ifndef CSM_TOOLS_MERGESTAGES_H #define CSM_TOOLS_MERGESTAGES_H #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 (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.47.0/apps/opencs/model/tools/mergestate.hpp000066400000000000000000000010131413061077700237560ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/pathgridcheck.cpp000066400000000000000000000123241413061077700244200ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/pathgridcheck.hpp000066400000000000000000000017051413061077700244260ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/racecheck.cpp000066400000000000000000000047141413061077700235340ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/racecheck.hpp000066400000000000000000000016501413061077700235350ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/referenceablecheck.cpp000066400000000000000000001214651413061077700254070ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/referenceablecheck.hpp000066400000000000000000000140201413061077700254000ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/referencecheck.cpp000066400000000000000000000074411413061077700245600ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/referencecheck.hpp000066400000000000000000000017631413061077700245660ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/regioncheck.cpp000066400000000000000000000034361413061077700241050ustar00rootroot00000000000000#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.mA + region.mData.mB; 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.47.0/apps/opencs/model/tools/regioncheck.hpp000066400000000000000000000014251413061077700241060ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/reportmodel.cpp000066400000000000000000000110561413061077700241550ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/reportmodel.hpp000066400000000000000000000031661413061077700241650ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/scriptcheck.cpp000066400000000000000000000074671413061077700241360ustar00rootroot00000000000000#include "scriptcheck.hpp" #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.47.0/apps/opencs/model/tools/scriptcheck.hpp000066400000000000000000000030371413061077700241300ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/search.cpp000066400000000000000000000215751413061077700230750ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/search.hpp000066400000000000000000000056621413061077700231010ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/searchoperation.cpp000066400000000000000000000022141413061077700250030ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/searchoperation.hpp000066400000000000000000000014701413061077700250130ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/searchstage.cpp000066400000000000000000000011621413061077700241070ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/searchstage.hpp000066400000000000000000000014311413061077700241130ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/skillcheck.cpp000066400000000000000000000022721413061077700237350ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/skillcheck.hpp000066400000000000000000000014141413061077700237370ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/soundcheck.cpp000066400000000000000000000027131413061077700237470ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/soundcheck.hpp000066400000000000000000000016461413061077700237600ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/soundgencheck.cpp000066400000000000000000000040341413061077700244370ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/soundgencheck.hpp000066400000000000000000000020261413061077700244430ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/spellcheck.cpp000066400000000000000000000024441413061077700237370ustar00rootroot00000000000000#include "spellcheck.hpp" #include #include #include #include "../prefs/state.hpp" #include "../world/universalid.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.47.0/apps/opencs/model/tools/spellcheck.hpp000066400000000000000000000014141413061077700237400ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/startscriptcheck.cpp000066400000000000000000000023061413061077700251770ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/startscriptcheck.hpp000066400000000000000000000014261413061077700252060ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/tools.cpp000066400000000000000000000251211413061077700227570ustar00rootroot00000000000000#include "tools.hpp" #include #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.47.0/apps/opencs/model/tools/tools.hpp000066400000000000000000000057231413061077700227720ustar00rootroot00000000000000#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.47.0/apps/opencs/model/tools/topicinfocheck.cpp000066400000000000000000000326731413061077700246210ustar00rootroot00000000000000#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, T::getRecordType() + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { messages.add(id, "Deleted " + T::getRecordType() + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } return true; } openmw-openmw-0.47.0/apps/opencs/model/tools/topicinfocheck.hpp000066400000000000000000000066761413061077700246320ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/000077500000000000000000000000001413061077700211015ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/model/world/actoradapter.cpp000066400000000000000000000526001413061077700242610ustar00rootroot00000000000000#include "actoradapter.hpp" #include #include #include #include #include #include #include "data.hpp" 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); } const std::string 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 ""; } const std::string& partName = it->second.first; return partName; } 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.reset(new ActorData()); 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.reset(new RaceData()); 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.47.0/apps/opencs/model/world/actoradapter.hpp000066400000000000000000000147701413061077700242740ustar00rootroot00000000000000#ifndef CSM_WOLRD_ACTORADAPTER_H #define CSM_WOLRD_ACTORADAPTER_H #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 const std::string 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.47.0/apps/opencs/model/world/cell.cpp000066400000000000000000000005041413061077700225230ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/cell.hpp000066400000000000000000000007531413061077700225360ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/cellcoordinates.cpp000066400000000000000000000114741413061077700247660ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/cellselection.cpp000066400000000000000000000033341413061077700244350ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/cellselection.hpp000066400000000000000000000030531413061077700244400ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/collection.hpp000066400000000000000000000463451413061077700237610ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLLECTION_H #define CSM_WOLRD_COLLECTION_H #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::map& getIdMap() const; 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 (const std::string& id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) void replace (int index, const RecordBase& record) override; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. void appendRecord (const RecordBase& 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 (const RecordBase& 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, const Record& record); ///< \attention This function must not change the ID. NestableColumn *getNestableColumn (int column) const; }; template const std::map& Collection::getIdMap() const { return mIndex; } 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; i::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) { Record copy; 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(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().mPlugin = 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().mPlugin = 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()) { Record record2; record2.mState = Record::State_ModifiedOnly; record2.mModified = record; insertRecord (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(); Record record2; record2.mState = Record::State_ModifiedOnly; record2.mModified = record; insertRecord (record2, getAppendIndex (id, type), type); } template int Collection::searchId (const std::string& 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, const RecordBase& record) { mRecords.at (index) = dynamic_cast&> (record); } template void Collection::appendRecord (const RecordBase& record, UniversalId::Type type) { insertRecord (record, getAppendIndex (IdAccessorT().getId ( dynamic_cast&> (record).get()), type), 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 (const RecordBase& record, int index, UniversalId::Type type) { if (index<0 || index>static_cast (mRecords.size())) throw std::runtime_error ("index out of range"); const Record& record2 = dynamic_cast&> (record); mRecords.insert (mRecords.begin()+index, record2); if (index (mRecords.size())-1) { for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter) if (iter->second>=index) ++(iter->second); } mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( record2.get())), index)); } template void Collection::setRecord (int index, const Record& 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) = record; } template bool Collection::reorderRows (int baseIndex, const std::vector& newOrder) { return false; } } #endif openmw-openmw-0.47.0/apps/opencs/model/world/collectionbase.cpp000066400000000000000000000011521413061077700245720ustar00rootroot00000000000000#include "collectionbase.hpp" #include #include "columnbase.hpp" CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {} int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const { int columns = getColumns(); for (int i=0; i #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 (const std::string& id) const = 0; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) virtual void replace (int index, const RecordBase& 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 (const RecordBase& 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? 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.47.0/apps/opencs/model/world/columnbase.cpp000066400000000000000000000072201413061077700237360ustar00rootroot00000000000000#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_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.47.0/apps/opencs/model/world/columnbase.hpp000066400000000000000000000163131413061077700237460ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLUMNBASE_H #define CSM_WOLRD_COLUMNBASE_H #include #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_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.47.0/apps/opencs/model/world/columnimp.cpp000066400000000000000000000167761413061077700236310ustar00rootroot00000000000000#include "columnimp.hpp" #include #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().mPlugin; } 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.47.0/apps/opencs/model/world/columnimp.hpp000066400000000000000000002145401413061077700236230ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLUMNIMP_H #define CSM_WOLRD_COLUMNIMP_H #include #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() : Column (Columns::ColumnId_Name, ColumnBase::Display_String) {} 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.47.0/apps/opencs/model/world/columns.cpp000066400000000000000000000660261413061077700232770ustar00rootroot00000000000000#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, "Wander 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,"Item Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item 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_NpcPersistence, "Persistent" }, { 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" }, { -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(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.47.0/apps/opencs/model/world/columns.hpp000066400000000000000000000336161413061077700233030ustar00rootroot00000000000000#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, ColumnId_NpcPersistence = 261, 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, // 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.47.0/apps/opencs/model/world/commanddispatcher.cpp000066400000000000000000000245211413061077700252760ustar00rootroot00000000000000#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; std::unique_ptr modifyDataRefNum; 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 and (if necessary) clear the instance's refNum modifyCell.reset (new UpdateCellCommand (model2, row)); // Not sure which model this should be applied to int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); if (refNumColumn!=-1) modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); } } } } std::unique_ptr modifyData ( new CSMWorld::ModifyCommand (*model, index, new_)); if (modifyCell.get()) { CommandMacro macro (mDocument.getUndoStack()); macro.push (modifyData.release()); macro.push (modifyCell.release()); if (modifyDataRefNum.get()) macro.push (modifyDataRefNum.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.47.0/apps/opencs/model/world/commanddispatcher.hpp000066400000000000000000000043321413061077700253010ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/commandmacro.cpp000066400000000000000000000010761413061077700242510ustar00rootroot00000000000000 #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.47.0/apps/opencs/model/world/commandmacro.hpp000066400000000000000000000013341413061077700242530ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/commands.cpp000066400000000000000000000401721413061077700234120ustar00rootroot00000000000000#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()); mOld.reset(mTable.getRecord(mId).clone()); } void CSMWorld::TouchCommand::redo() { mChanged = mTable.touchRecord(mId); } void CSMWorld::TouchCommand::undo() { if (mChanged) { mTable.setRecord(mId, *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()); mOld.reset(mLands.getRecord(mId).clone()); } 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); } void CSMWorld::TouchLandCommand::onUndo() { if (mChanged) { mLands.setRecord(mId, *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 (&model)) { // Replace proxy with actual model mIndex = proxy->mapToSource (index); mModel = proxy->sourceModel(); } 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()); } } void CSMWorld::ModifyCommand::redo() { 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()); mOld = model.getRecord (id).clone(); } CSMWorld::RevertCommand::~RevertCommand() { delete mOld; } void CSMWorld::RevertCommand::redo() { 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, *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()); mOld = model.getRecord (id).clone(); } CSMWorld::DeleteCommand::~DeleteCommand() { delete mOld; } void CSMWorld::DeleteCommand::redo() { 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, *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(); Record record = 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, 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); mModel.removeRows (mNestedRow, 1, parentIndex); mModifyParentCommand->redo(); } 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); mModel.addNestedRow (parentIndex, mNewRow); mModifyParentCommand->redo(); } 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.47.0/apps/opencs/model/world/commands.hpp000066400000000000000000000225431413061077700234210ustar00rootroot00000000000000#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 QModelIndex; 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; RecordBase *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; RecordBase *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.47.0/apps/opencs/model/world/data.cpp000066400000000000000000001621141413061077700225230ustar00rootroot00000000000000#include "data.hpp" #include #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.reset(new VFS::Manager(mFsStrict)); VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); mResourcesManager.setVFS(mVFS.get()); mResourceSystem.reset(new Resource::ResourceSystem(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"; 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); 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); 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.reset(new ActorAdapter(*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) { Record record (RecordBase::State_ModifiedOnly, nullptr, &metaData); mMetaData.setRecord (0, record); } 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::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, 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; CSMWorld::Record record; record.mBase = newMarker; record.mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (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; CSMWorld::Record record; record.mBase = newMarker; record.mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (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.intval) { 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.47.0/apps/opencs/model/world/data.hpp000066400000000000000000000246041413061077700225310ustar00rootroot00000000000000#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; SubCellCollection mPathgrids; IdCollection mDebugProfiles; IdCollection mSoundGens; IdCollection mStartScripts; NestedInfoCollection mTopicInfos; InfoCollection mJournalInfos; NestedIdCollection mCells; 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); virtual ~Data(); 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 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.47.0/apps/opencs/model/world/defaultgmsts.cpp000066400000000000000000001773751413061077700243330ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/defaultgmsts.hpp000066400000000000000000000015451413061077700243210ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idcollection.hpp000066400000000000000000000113671413061077700242720ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDCOLLECTION_H #define CSM_WOLRD_IDCOLLECTION_H #include #include "collection.hpp" #include "land.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; } Record baseRecord = this->getRecord (index); baseRecord.mState = RecordBase::State_Deleted; this->setRecord (index, baseRecord); return index; } return load (record, base, index); } template int IdCollection::load (const ESXRecordT& record, bool base, int index) { if (index==-2) index = this->searchId (IdAccessorT().getId (record)); if (index==-1) { // new record Record record2; record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; index = this->getSize(); this->appendRecord (record2); } else { // old record Record record2 = Collection::getRecord (index); if (base) record2.mBase = record; else record2.setModified (record); this->setRecord (index, record2); } return index; } template bool IdCollection::tryDelete (const std::string& id) { int index = this->searchId (id); if (index==-1) return false; Record record = Collection::getRecord (index); if (record.isDeleted()) return false; if (record.mState==RecordBase::State_ModifiedOnly) { Collection::removeRows (index, 1); } else { record.mState = RecordBase::State_Deleted; this->setRecord (index, record); } return true; } } #endif openmw-openmw-0.47.0/apps/opencs/model/world/idcompletionmanager.cpp000066400000000000000000000130011413061077700256210ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idcompletionmanager.hpp000066400000000000000000000020251413061077700256320ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idtable.cpp000066400000000000000000000265121413061077700232170ustar00rootroot00000000000000#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; 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); 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, const RecordBase& record, CSMWorld::UniversalId::Type type) { int index = mIdCollection->searchId (id); if (index==-1) { index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); mIdCollection->appendRecord (record, type); endInsertRows(); } else { mIdCollection->replace (index, 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 = record.get().mTexture; std::transform(texture.begin(), texture.end(), texture.begin(), tolower); 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 = record.get().mTexture; std::transform(texture.begin(), texture.end(), texture.begin(), tolower); 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.47.0/apps/opencs/model/world/idtable.hpp000066400000000000000000000113151413061077700232170ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H #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, const RecordBase& 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.47.0/apps/opencs/model/world/idtablebase.cpp000066400000000000000000000003041413061077700240410ustar00rootroot00000000000000#include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} unsigned int CSMWorld::IdTableBase::getFeatures() const { return mFeatures; } openmw-openmw-0.47.0/apps/opencs/model/world/idtablebase.hpp000066400000000000000000000041301413061077700240470ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idtableproxymodel.cpp000066400000000000000000000107271413061077700253430ustar00rootroot00000000000000#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() { 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.47.0/apps/opencs/model/world/idtableproxymodel.hpp000066400000000000000000000035711413061077700253470ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idtree.cpp000066400000000000000000000215351413061077700230670ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/idtree.hpp000066400000000000000000000065651413061077700231020ustar00rootroot00000000000000#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. virtual ~IdTree(); 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.47.0/apps/opencs/model/world/info.hpp000066400000000000000000000003141413061077700225430ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/infocollection.cpp000066400000000000000000000142011413061077700246120ustar00rootroot00000000000000#include "infocollection.hpp" #include #include #include #include #include void CSMWorld::InfoCollection::load (const Info& record, bool base) { int index = searchId (record.mId); if (index==-1) { // new record Record record2; record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); if (!record2.get().mPrev.empty()) { index = getInfoIndex (record2.get().mPrev, topic); if (index!=-1) ++index; } if (index==-1 && !record2.get().mNext.empty()) { index = getInfoIndex (record2.get().mNext, topic); } if (index==-1) { Range range = getTopicRange (topic); index = std::distance (getRecords().begin(), range.second); } insertRecord (record2, index); } else { // old record Record record2 = getRecord (index); if (base) record2.mBase = record; else record2.setModified (record); setRecord (index, record2); } } int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const { std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; std::pair range = getTopicRange (topic); for (; range.first!=range.second; ++range.first) if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId)) return std::distance (getRecords().begin(), range.first); return -1; } int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const { 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); } 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 return reorderRowsImp (baseIndex, newOrder); } 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 { Record record = getRecord (index); record.mState = RecordBase::State_Deleted; setRecord (index, 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 topic2 = Misc::StringUtils::lowerCase (topic); std::map::const_iterator iter = getIdMap().lower_bound (topic2); // Skip invalid records: The beginning of a topic string could be identical to another topic // string. for (; iter!=getIdMap().end(); ++iter) { std::string testTopicId = Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId); if (testTopicId==topic2) break; std::size_t size = topic2.size(); if (testTopicId.size()second; while (begin != getRecords().begin()) { if (!Misc::StringUtils::ciEqual(begin->get().mTopicId, topic2)) { // we've gone one too far, go back ++begin; break; } --begin; } // Find end RecordConstIterator end = begin; for (; end!=getRecords().end(); ++end) if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2)) break; return Range (begin, end); } void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) { std::string id = Misc::StringUtils::lowerCase(dialogueId); std::vector erasedRecords; std::map::const_iterator current = getIdMap().lower_bound(id); std::map::const_iterator end = getIdMap().end(); for (; current != end; ++current) { Record record = getRecord(current->second); if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) { if (record.mState == RecordBase::State_ModifiedOnly) { erasedRecords.push_back(current->second); } else { record.mState = RecordBase::State_Deleted; setRecord(current->second, record); } } else { break; } } while (!erasedRecords.empty()) { removeRows(erasedRecords.back(), 1); erasedRecords.pop_back(); } } openmw-openmw-0.47.0/apps/opencs/model/world/infocollection.hpp000066400000000000000000000033101413061077700246160ustar00rootroot00000000000000#ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H #include "collection.hpp" #include "info.hpp" namespace ESM { struct Dialogue; } namespace CSMWorld { class InfoCollection : public Collection > { public: typedef std::vector >::const_iterator RecordConstIterator; typedef std::pair Range; private: void load (const Info& record, bool base); int getInfoIndex (const std::string& id, const std::string& topic) const; ///< Return index for record \a id or -1 (if not present; deleted records are considered) /// /// \param id info ID without topic prefix public: 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 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); }; } #endif openmw-openmw-0.47.0/apps/opencs/model/world/infoselectwrapper.cpp000066400000000000000000000621771413061077700253560ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/infoselectwrapper.hpp000066400000000000000000000165031413061077700253530ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/infotableproxymodel.cpp000066400000000000000000000071621413061077700257010ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/infotableproxymodel.hpp000066400000000000000000000026051413061077700257030ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/land.cpp000066400000000000000000000012661413061077700225300ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/land.hpp000066400000000000000000000010741413061077700225320ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/landtexture.cpp000066400000000000000000000015171413061077700241500ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/landtexture.hpp000066400000000000000000000013271413061077700241540ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/landtexturetableproxymodel.cpp000066400000000000000000000006351413061077700273030ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/landtexturetableproxymodel.hpp000066400000000000000000000010111413061077700272750ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/metadata.cpp000066400000000000000000000011521413061077700233640ustar00rootroot00000000000000#include "metadata.hpp" #include #include #include void CSMWorld::MetaData::blank() { mFormat = ESM::Header::CurrentFormat; 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.47.0/apps/opencs/model/world/metadata.hpp000066400000000000000000000006511413061077700233740ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedcoladapterimp.cpp000066400000000000000000001214041413061077700256360ustar00rootroot00000000000000#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.mA; // Snow case 9: return region.mData.mB; // Blizzard 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.mA = chance; break; case 9: region.mData.mB = 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.47.0/apps/opencs/model/world/nestedcoladapterimp.hpp000066400000000000000000000505531413061077700256510ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedcollection.cpp000066400000000000000000000021061413061077700251420ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedcollection.hpp000066400000000000000000000026741413061077700251610ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedcolumnadapter.hpp000066400000000000000000000022541413061077700256560ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedidcollection.hpp000066400000000000000000000160671413061077700254770ustar00rootroot00000000000000#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(); 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) { Record record; record.assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).addRow(record, position); Collection::setRecord(row, record); } template void NestedIdCollection::removeNestedRows(int row, int column, int subRow) { Record record; record.assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).removeRow(record, subRow); Collection::setRecord(row, 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) { Record record; record.assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setData( record, data, subRow, subColumn); Collection::setRecord(row, 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) { Record record; record.assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setTable( record, nestedTable); Collection::setRecord(row, 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.47.0/apps/opencs/model/world/nestedinfocollection.cpp000066400000000000000000000074711413061077700260300ustar00rootroot00000000000000#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) { Record record; record.assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).addRow(record, position); Collection >::setRecord(row, record); } void NestedInfoCollection::removeNestedRows(int row, int column, int subRow) { Record record; record.assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).removeRow(record, subRow); Collection >::setRecord(row, 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) { Record record; record.assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setData( record, data, subRow, subColumn); Collection >::setRecord(row, 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) { Record record; record.assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setTable( record, nestedTable); Collection >::setRecord(row, 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.47.0/apps/opencs/model/world/nestedinfocollection.hpp000066400000000000000000000032041413061077700260230ustar00rootroot00000000000000#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(); 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.47.0/apps/opencs/model/world/nestedtableproxymodel.cpp000066400000000000000000000144441413061077700262310ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedtableproxymodel.hpp000066400000000000000000000047561413061077700262430ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/nestedtablewrapper.cpp000066400000000000000000000003501413061077700254760ustar00rootroot00000000000000#include "nestedtablewrapper.hpp" CSMWorld::NestedTableWrapperBase::NestedTableWrapperBase() {} CSMWorld::NestedTableWrapperBase::~NestedTableWrapperBase() {} int CSMWorld::NestedTableWrapperBase::size() const { return -5; } openmw-openmw-0.47.0/apps/opencs/model/world/nestedtablewrapper.hpp000066400000000000000000000013041413061077700255030ustar00rootroot00000000000000#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) {} virtual ~NestedTableWrapper() {} int size() const override { return mNestedTable.size(); //i hope that this will be enough } }; } #endif openmw-openmw-0.47.0/apps/opencs/model/world/pathgrid.cpp000066400000000000000000000013251413061077700234100ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/pathgrid.hpp000066400000000000000000000012611413061077700234140ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/record.cpp000066400000000000000000000005611413061077700230650ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/record.hpp000066400000000000000000000102241413061077700230670ustar00rootroot00000000000000#ifndef CSM_WOLRD_RECORD_H #define CSM_WOLRD_RECORD_H #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 RecordBase *clone() const = 0; virtual RecordBase *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); RecordBase *clone() const override; RecordBase *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 RecordBase *Record::modifiedCopy() const { return new Record (State_ModifiedOnly, nullptr, &(this->get())); } template RecordBase *Record::clone() const { return new 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.47.0/apps/opencs/model/world/ref.cpp000066400000000000000000000004511413061077700223610ustar00rootroot00000000000000#include "ref.hpp" #include "cellcoordinates.hpp" CSMWorld::CellRef::CellRef() : mNew (true) { mRefNum.mIndex = 0; mRefNum.mContentFile = 0; } std::pair CSMWorld::CellRef::getCellIndex() const { return CellCoordinates::coordinatesToCellIndex (mPos.pos[0], mPos.pos[1]); } openmw-openmw-0.47.0/apps/opencs/model/world/ref.hpp000066400000000000000000000010341413061077700223640ustar00rootroot00000000000000#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 CellRef(); /// Calculate cell index based on coordinates (x and y) std::pair getCellIndex() const; }; } #endif openmw-openmw-0.47.0/apps/opencs/model/world/refcollection.cpp000066400000000000000000000111561413061077700244410ustar00rootroot00000000000000#include "refcollection.hpp" #include #include "ref.hpp" #include "cell.hpp" #include "universalid.hpp" #include "record.hpp" 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; while (ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // 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 && mref.mRefNum.mIndex != 0) { // 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; mref.mRefNum.mIndex = 0; // ignore content file number std::map::iterator iter = cache.begin(); unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff; if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; for (; iter != cache.end(); ++iter) { if (thisIndex == iter->first.mIndex) break; } 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"); continue; } int index = getIndex (iter->second); Record record = getRecord (index); if (base) { removeRows (index, 1); cache.erase (iter); } else { record.mState = RecordBase::State_Deleted; setRecord (index, record); } continue; } if (iter==cache.end()) { // new reference ref.mId = getNewId(); Record record; record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; const ESM::RefNum refNum = ref.mRefNum; std::string refId = ref.mId; (base ? record.mBase : record.mModified) = std::move(ref); appendRecord (record); cache.emplace(refNum, std::move(refId)); } else { // old reference -> merge ref.mId = iter->second; int index = getIndex (ref.mId); Record record = getRecord (index); record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; (base ? record.mBase : record.mModified) = std::move(ref); setRecord (index, record); } } } std::string CSMWorld::RefCollection::getNewId() { return "ref#" + std::to_string(mNextId++); } openmw-openmw-0.47.0/apps/opencs/model/world/refcollection.hpp000066400000000000000000000015751413061077700244520ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFCOLLECTION_H #define CSM_WOLRD_REFCOLLECTION_H #include #include "../doc/stage.hpp" #include "collection.hpp" #include "ref.hpp" #include "record.hpp" namespace CSMWorld { struct Cell; class UniversalId; /// \brief References in cells class RefCollection : public Collection { Collection& mCells; int mNextId; 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(); }; } #endif openmw-openmw-0.47.0/apps/opencs/model/world/refidadapter.cpp000066400000000000000000000003621413061077700242400ustar00rootroot00000000000000#include "refidadapter.hpp" CSMWorld::RefIdAdapter::RefIdAdapter() {} CSMWorld::RefIdAdapter::~RefIdAdapter() {} CSMWorld::NestedRefIdAdapterBase::NestedRefIdAdapterBase() {} CSMWorld::NestedRefIdAdapterBase::~NestedRefIdAdapterBase() {} openmw-openmw-0.47.0/apps/opencs/model/world/refidadapter.hpp000066400000000000000000000057241413061077700242540ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/refidadapterimp.cpp000066400000000000000000001562621413061077700247610ustar00rootroot00000000000000#include "refidadapterimp.hpp" #include #include #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; case 8: return record.get().mPersistent == true; 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; case 8: return record.get().mPersistent == true; 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; case 8: npc.mPersistent = value.toBool(); 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; case 8: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } record.setModified (npc); } int CSMWorld::NpcMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 9; // Level, Health, Mana, Fatigue, Disposition, Reputation, Rank, Gold, Persist } 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.47.0/apps/opencs/model/world/refidadapterimp.hpp000066400000000000000000002671041413061077700247640ustar00rootroot00000000000000#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; }; /// \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); 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()); } template UniversalId::Type BaseRefIdAdapter::getType() const { return mType; } struct ModelColumns : public BaseColumns { const RefIdColumn *mModel; ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(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()); 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 { 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(); virtual ~IngredEffectRefIdAdapter(); 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 (); virtual ~NpcMiscRefIdAdapter(); 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 (); virtual ~CreatureMiscRefIdAdapter(); 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 = ""; 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 = 0; newRow.mCellName = ""; 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: // wander repeat if (content.mType == ESM::AI_Wander) return content.mWander.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 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 return; // return without saving break; // always save case 13: // NAME32 if (content.mType == ESM::AI_Activate) content.mActivate.mName.assign(value.toString().toUtf8().constData()); else return; // return without saving break; // always save case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mId.assign(value.toString().toUtf8().constData()); 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 = ""; newPart.mFemale = ""; 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 = ""; 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.47.0/apps/opencs/model/world/refidcollection.cpp000066400000000000000000001253261413061077700247630ustar00rootroot00000000000000#include "refidcollection.hpp" #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(); ModelColumns modelColumns (baseColumns); mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); modelColumns.mModel = &mColumns.back(); NameColumns nameColumns (modelColumns); mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String); 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_String)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String)); 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(); 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)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); 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 levListColumns (baseColumns); // Nested table mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); levListColumns.mLevList = &mColumns.back(); std::map levListMap; levListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, new NestedLevListRefIdAdapter (UniversalId::Type_CreatureLevelledList))); levListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, new NestedLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); mNestedAdapters.emplace_back(&mColumns.back(), levListMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); // Nested list mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); levListColumns.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, levListColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_ItemLevelledList, new LevelledListRefIdAdapter (UniversalId::Type_ItemLevelledList, levListColumns))); 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); return; } 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); return; } void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) { mData.appendRecord (type, id, false); } int CSMWorld::RefIdCollection::searchId (const std::string& id) const { RefIdData::LocalIndex localIndex = mData.searchId (id); if (localIndex.first==-1) return -1; return mData.localToGlobalIndex (localIndex); } void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) { mData.getRecord (mData.globalToLocalIndex (index)).assign (record); } 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(*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 (const RecordBase& record, UniversalId::Type type) { std::string id = findAdapter (type).getId (record); int index = mData.getAppendIndex (type); mData.appendRecord (type, id, false); mData.getRecord (mData.globalToLocalIndex (index)).assign (record); } 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); return; } 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); return; } 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.47.0/apps/opencs/model/world/refidcollection.hpp000066400000000000000000000121601413061077700247570ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFIDCOLLECTION_H #define CSM_WOLRD_REFIDCOLLECTION_H #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(); virtual ~RefIdCollection(); 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 (const std::string& id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) void replace (int index, const RecordBase& record) override; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. void appendRecord (const RecordBase& 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.47.0/apps/opencs/model/world/refiddata.cpp000066400000000000000000000306211413061077700235320ustar00rootroot00000000000000#include "refiddata.hpp" #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 ( const std::string& 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; } 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 (CSMWorld::RecordBase& 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(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; std::string id = source->getId (localIndex.first); std::unique_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); target.insertRecord (*newRecord, localIndex.second, id); } openmw-openmw-0.47.0/apps/opencs/model/world/refiddata.hpp000066400000000000000000000246051413061077700235440ustar00rootroot00000000000000#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 "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 void appendRecord (const std::string& id, bool base) = 0; virtual void insertRecord (RecordBase& 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; void appendRecord (const std::string& id, bool base) override; void insertRecord (RecordBase& 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(RecordBase& record) { Record& newRecord = dynamic_cast& >(record); mContainer.push_back(newRecord); } 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 void RefIdDataContainer::appendRecord (const std::string& id, bool base) { Record record; 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 (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 { Record record = mContainer.at(index); if (record.isModified() || record.mState == RecordBase::State_Deleted) { RecordT esmRecord = record.get(); writer.startRecord(esmRecord.sRecordId); 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 (const std::string& id) const; void erase (int index, int count); void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; RecordBase& getRecord (const LocalIndex& index); 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.47.0/apps/opencs/model/world/regionmap.cpp000066400000000000000000000337101413061077700235720ustar00rootroot00000000000000#include "regionmap.hpp" #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.47.0/apps/opencs/model/world/regionmap.hpp000066400000000000000000000074311413061077700236000ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/resources.cpp000066400000000000000000000051471413061077700236260ustar00rootroot00000000000000#include "resources.hpp" #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(); const std::map& index = vfs->getIndex(); for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) { std::string filepath = it->first; 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 (const std::string& 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.47.0/apps/opencs/model/world/resources.hpp000066400000000000000000000017451413061077700236330ustar00rootroot00000000000000#ifndef CSM_WOLRD_RESOURCES_H #define CSM_WOLRD_RESOURCES_H #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 (const std::string& id) const; UniversalId::Type getType() const; }; } #endif openmw-openmw-0.47.0/apps/opencs/model/world/resourcesmanager.cpp000066400000000000000000000036651413061077700251640ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/resourcesmanager.hpp000066400000000000000000000013471413061077700251640ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/resourcetable.cpp000066400000000000000000000073671413061077700244610ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/resourcetable.hpp000066400000000000000000000044201413061077700244510ustar00rootroot00000000000000#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); virtual ~ResourceTable(); 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.47.0/apps/opencs/model/world/scope.cpp000066400000000000000000000007751413061077700227270ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/scope.hpp000066400000000000000000000006451413061077700227300ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/scriptcontext.cpp000066400000000000000000000070371413061077700245250ustar00rootroot00000000000000#include "scriptcontext.hpp" #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)); } bool CSMWorld::ScriptContext::isJournalId (const std::string& name) const { return mData.getJournals().searchId (name)!=-1; } 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.47.0/apps/opencs/model/world/scriptcontext.hpp000066400000000000000000000032611413061077700245250ustar00rootroot00000000000000#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? bool isJournalId (const std::string& name) const override; ///< Does \a name match a journal ID? 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.47.0/apps/opencs/model/world/subcellcollection.hpp000066400000000000000000000024471413061077700253260ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/tablemimedata.cpp000066400000000000000000000276421413061077700244110ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/tablemimedata.hpp000066400000000000000000000037761413061077700244200ustar00rootroot00000000000000#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.47.0/apps/opencs/model/world/universalid.cpp000066400000000000000000000476421413061077700241470ustar00rootroot00000000000000#include "universalid.hpp" #include #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); } std::ostream& CSMWorld::operator< (std::ostream& stream, const CSMWorld::UniversalId& universalId) { return stream << universalId.toString(); } openmw-openmw-0.47.0/apps/opencs/model/world/universalid.hpp000066400000000000000000000145011413061077700241400ustar00rootroot00000000000000#ifndef CSM_WOLRD_UNIVERSALID_H #define CSM_WOLRD_UNIVERSALID_H #include #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); std::ostream& operator< (std::ostream& stream, const UniversalId& universalId); } Q_DECLARE_METATYPE (CSMWorld::UniversalId) #endif openmw-openmw-0.47.0/apps/opencs/view/000077500000000000000000000000001413061077700176245ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/doc/000077500000000000000000000000001413061077700203715ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/doc/adjusterwidget.cpp000066400000000000000000000062611413061077700241270ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/adjusterwidget.hpp000066400000000000000000000022571413061077700241350ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/filedialog.cpp000066400000000000000000000135071413061077700232020ustar00rootroot00000000000000#include "filedialog.hpp" #include #include #include #include #include #include #include #include #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); mAdjusterWidget = new AdjusterWidget (this); } void CSVDoc::FileDialog::addFiles(const QString &path) { mSelector->addFiles(path); } 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 = 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.47.0/apps/opencs/view/doc/filedialog.hpp000066400000000000000000000033541413061077700232060ustar00rootroot00000000000000#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 QString &path); 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.47.0/apps/opencs/view/doc/filewidget.cpp000066400000000000000000000024151413061077700232220ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/filewidget.hpp000066400000000000000000000014271413061077700232310ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/globaldebugprofilemenu.cpp000066400000000000000000000053331413061077700256160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/globaldebugprofilemenu.hpp000066400000000000000000000017641413061077700256270ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/loader.cpp000066400000000000000000000122121413061077700223410ustar00rootroot00000000000000#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), mAborted (false), mMessages (nullptr), mTotalRecords (0) { setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); setMinimumWidth (400); mLayout = new QVBoxLayout (this); // file progress mFile = new QLabel (this); mLayout->addWidget (mFile); mFileProgress = new QProgressBar (this); mLayout->addWidget (mFileProgress); int size = static_cast (document->getContentFiles().size())+1; if (document->isNew()) --size; mFileProgress->setMinimum (0); mFileProgress->setMaximum (size); mFileProgress->setTextVisible (true); mFileProgress->setValue (0); // record progress mLayout->addWidget (mRecords = 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 totalRecords) { mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); mFileProgress->setValue (mFileProgress->value()+1); mRecordProgress->setValue (0); mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); mTotalRecords = totalRecords; } void CSVDoc::LoadingDocument::nextRecord (int records) { if (records<=mTotalRecords) { mRecordProgress->setValue (records); std::ostringstream stream; stream << "Records: " << records << " of " << mTotalRecords; mRecords->setText (QString::fromUtf8 (stream.str().c_str())); } } 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 totalRecords) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) iter->second->nextStage (name, totalRecords); } 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.47.0/apps/opencs/view/doc/loader.hpp000066400000000000000000000042311413061077700223500ustar00rootroot00000000000000#ifndef CSV_DOC_LOADER_H #define CSV_DOC_LOADER_H #include #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 *mFile; QLabel *mRecords; QProgressBar *mFileProgress; QProgressBar *mRecordProgress; bool mAborted; QDialogButtonBox *mButtons; QLabel *mError; QListWidget *mMessages; QVBoxLayout *mLayout; int mTotalRecords; 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(); virtual ~Loader(); 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.47.0/apps/opencs/view/doc/newgame.cpp000066400000000000000000000036601413061077700225250ustar00rootroot00000000000000#include "newgame.hpp" #include #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.47.0/apps/opencs/view/doc/newgame.hpp000066400000000000000000000016711413061077700225320ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/operation.cpp000066400000000000000000000073771413061077700231130ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/operation.hpp000066400000000000000000000020111413061077700230740ustar00rootroot00000000000000#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(); 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.47.0/apps/opencs/view/doc/operations.cpp000066400000000000000000000037171413061077700232700ustar00rootroot00000000000000#include "operations.hpp" #include #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.47.0/apps/opencs/view/doc/operations.hpp000066400000000000000000000015351413061077700232710ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/runlogsubview.cpp000066400000000000000000000006601413061077700240120ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/runlogsubview.hpp000066400000000000000000000005421413061077700240160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/sizehint.cpp000066400000000000000000000004551413061077700227360ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/sizehint.hpp000066400000000000000000000006451413061077700227440ustar00rootroot00000000000000#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(); QSize sizeHint() const override; void setSizeHint(const QSize &size); }; } #endif // CSV_DOC_SIZEHINT_H openmw-openmw-0.47.0/apps/opencs/view/doc/startup.cpp000066400000000000000000000073451413061077700226100ustar00rootroot00000000000000#include "startup.hpp" #include #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.47.0/apps/opencs/view/doc/startup.hpp000066400000000000000000000013101413061077700225770ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/subview.cpp000066400000000000000000000026301413061077700225620ustar00rootroot00000000000000#include "subview.hpp" #include "view.hpp" #include #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.47.0/apps/opencs/view/doc/subview.hpp000066400000000000000000000031051413061077700225650ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/subviewfactory.cpp000066400000000000000000000022151413061077700241510ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/subviewfactory.hpp000066400000000000000000000026451413061077700241650ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/subviewfactoryimp.hpp000066400000000000000000000026451413061077700246730ustar00rootroot00000000000000#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.47.0/apps/opencs/view/doc/view.cpp000066400000000000000000001064461413061077700220620ustar00rootroot00000000000000#include "view.hpp" #include #include #include #include #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 "../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; 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())); 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* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); 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())); 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(); // items that don't represent single record lists follow here 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* 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())); QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); 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())); QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); } 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* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); } 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&))); } 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.47.0/apps/opencs/view/doc/view.hpp000066400000000000000000000140471413061077700220620ustar00rootroot00000000000000#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. virtual ~View(); 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.47.0/apps/opencs/view/doc/viewmanager.cpp000066400000000000000000000413131413061077700234040ustar00rootroot00000000000000#include "viewmanager.hpp" #include #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.47.0/apps/opencs/view/doc/viewmanager.hpp000066400000000000000000000044351413061077700234150ustar00rootroot00000000000000#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); virtual ~ViewManager(); 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.47.0/apps/opencs/view/filter/000077500000000000000000000000001413061077700211115ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/filter/editwidget.cpp000066400000000000000000000146371413061077700237610ustar00rootroot00000000000000#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); } 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.47.0/apps/opencs/view/filter/editwidget.hpp000066400000000000000000000027721413061077700237630ustar00rootroot00000000000000#ifndef CSV_FILTER_EDITWIDGET_H #define CSV_FILTER_EDITWIDGET_H #include #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.47.0/apps/opencs/view/filter/filterbox.cpp000066400000000000000000000032321413061077700236130ustar00rootroot00000000000000#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.47.0/apps/opencs/view/filter/filterbox.hpp000066400000000000000000000022301413061077700236150ustar00rootroot00000000000000#ifndef CSV_FILTER_FILTERBOX_H #define CSV_FILTER_FILTERBOX_H #include #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.47.0/apps/opencs/view/filter/recordfilterbox.cpp000066400000000000000000000021201413061077700250050ustar00rootroot00000000000000#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.47.0/apps/opencs/view/filter/recordfilterbox.hpp000066400000000000000000000015671413061077700250300ustar00rootroot00000000000000#ifndef CSV_FILTER_RECORDFILTERBOX_H #define CSV_FILTER_RECORDFILTERBOX_H #include #include #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.47.0/apps/opencs/view/prefs/000077500000000000000000000000001413061077700207435ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/prefs/contextmenulist.cpp000066400000000000000000000020761413061077700247210ustar00rootroot00000000000000#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.47.0/apps/opencs/view/prefs/contextmenulist.hpp000066400000000000000000000011241413061077700247170ustar00rootroot00000000000000#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.47.0/apps/opencs/view/prefs/dialogue.cpp000066400000000000000000000072571413061077700232530ustar00rootroot00000000000000#include "dialogue.hpp" #include #include #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.47.0/apps/opencs/view/prefs/dialogue.hpp000066400000000000000000000015061413061077700232470ustar00rootroot00000000000000#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(); virtual ~Dialogue(); protected: void closeEvent (QCloseEvent *event) override; public slots: void show(); private slots: void selectionChanged (QListWidgetItem *current, QListWidgetItem *previous); }; } #endif openmw-openmw-0.47.0/apps/opencs/view/prefs/keybindingpage.cpp000066400000000000000000000064111413061077700244310ustar00rootroot00000000000000#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.47.0/apps/opencs/view/prefs/keybindingpage.hpp000066400000000000000000000012221413061077700244310ustar00rootroot00000000000000#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.47.0/apps/opencs/view/prefs/page.cpp000066400000000000000000000016711413061077700223700ustar00rootroot00000000000000 #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.47.0/apps/opencs/view/prefs/page.hpp000066400000000000000000000006351413061077700223740ustar00rootroot00000000000000#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.47.0/apps/opencs/view/prefs/pagebase.cpp000066400000000000000000000015241413061077700232200ustar00rootroot00000000000000 #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.47.0/apps/opencs/view/prefs/pagebase.hpp000066400000000000000000000011441413061077700232230ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/000077500000000000000000000000001413061077700211035ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/render/actor.cpp000066400000000000000000000073431413061077700227260ustar00rootroot00000000000000#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; std::string 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); } } std::string Actor::getBodyPartMesh(const std::string& 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.47.0/apps/opencs/view/render/actor.hpp000066400000000000000000000030321413061077700227220ustar00rootroot00000000000000#ifndef OPENCS_VIEW_RENDER_ACTOR_H #define OPENCS_VIEW_RENDER_ACTOR_H #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(const std::string& 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.47.0/apps/opencs/view/render/brushdraw.cpp000066400000000000000000000265521413061077700236220ustar00rootroot00000000000000#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); 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; 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 = (osg::PI * 2.0f) * radius / 20; 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.47.0/apps/opencs/view/render/brushdraw.hpp000066400000000000000000000021341413061077700236150ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cameracontroller.cpp000066400000000000000000000530011413061077700251420ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cameracontroller.hpp000066400000000000000000000126061413061077700251550ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cell.cpp000066400000000000000000000437301413061077700225350ustar00rootroot00000000000000#include "cell.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/refcollection.hpp" #include "../../model/world/cellcoordinates.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); std::unique_ptr object (new Object (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.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain)); } mTerrain->loadCell(esmLand.mX, esmLand.mY); if (!mCellBorder) mCellBorder.reset(new CellBorder(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.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); mCellWater.reset(new CellWater(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< -1) { const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); cellExists = !cellRecord.isDeleted(); isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; } if (!isInteriorCell) { mCellMarker.reset(new CellMarker(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.47.0/apps/opencs/view/render/cell.hpp000066400000000000000000000135351413061077700225420ustar00rootroot00000000000000#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; class Geode; } 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.47.0/apps/opencs/view/render/cellarrow.cpp000066400000000000000000000130051413061077700236000ustar00rootroot00000000000000 #include "cellarrow.hpp" #include #include #include #include #include #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcutmanager.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 { 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 + 800; 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 = 4000; const int arrowLength = 1500; const int arrowHeight = 500; 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); osg::ref_ptr geode (new osg::Geode); geode->addDrawable (geometry); mBaseNode->addChild (geode); } 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.47.0/apps/opencs/view/render/cellarrow.hpp000066400000000000000000000027221413061077700236110ustar00rootroot00000000000000#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 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.47.0/apps/opencs/view/render/cellborder.cpp000066400000000000000000000065551413061077700237370ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cellborder.hpp000066400000000000000000000017011413061077700237300ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cellmarker.cpp000066400000000000000000000054471413061077700237420ustar00rootroot00000000000000#include "cellmarker.hpp" #include #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. osg::ref_ptr geode (new osg::Geode); geode->addDrawable(markerText); mMarkerNode->addChild(geode); } 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.47.0/apps/opencs/view/render/cellmarker.hpp000066400000000000000000000027611413061077700237430ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/cellwater.cpp000066400000000000000000000126631413061077700236010ustar00rootroot00000000000000#include "cellwater.hpp" #include #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) , mWaterNode(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); mWaterNode = new osg::Geode(); mWaterTransform->addChild(mWaterNode); 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) { mWaterNode->removeDrawable(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); mWaterNode->addDrawable(mWaterGeometry); } } openmw-openmw-0.47.0/apps/opencs/view/render/cellwater.hpp000066400000000000000000000030521413061077700235760ustar00rootroot00000000000000#ifndef CSV_RENDER_CELLWATER_H #define CSV_RENDER_CELLWATER_H #include #include #include #include #include "../../model/world/record.hpp" namespace osg { class Geode; 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 mWaterNode; osg::ref_ptr mWaterGeometry; bool mDeleted; bool mExterior; bool mHasWater; }; } #endif openmw-openmw-0.47.0/apps/opencs/view/render/commands.cpp000066400000000000000000000021151413061077700234070ustar00rootroot00000000000000#include "commands.hpp" #include #include #include #include "editmode.hpp" #include "terrainselection.hpp" #include "terrainshapemode.hpp" #include "terraintexturemode.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.47.0/apps/opencs/view/render/commands.hpp000066400000000000000000000023211413061077700234130ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/editmode.cpp000066400000000000000000000041301413061077700233770ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/editmode.hpp000066400000000000000000000065611413061077700234160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/instancedragmodes.hpp000066400000000000000000000005461413061077700253130ustar00rootroot00000000000000#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 }; } #endif openmw-openmw-0.47.0/apps/opencs/view/render/instancemode.cpp000066400000000000000000001044151413061077700242650ustar00rootroot00000000000000 #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; } 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
  • " "
" "Grid rotate not implemented yet"); 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
  • " "
" "Grid scale not implemented yet"); 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; } 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); 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 (mLocked) return false; return false; } 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) { osg::Vec3f eye, centre, up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); if (diffY) { offset += up * diffY * speedFactor; } if (diffX) { offset += ((centre-eye) ^ up) * diffX * speedFactor; } if (mDragAxis!=-1) { for (int i=0; i<3; ++i) { if (i!=mDragAxis) offset[i] = 0; } } } else if (mDragMode == DragMode_Rotate) { 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) { 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; } // Apply for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { if (mDragMode == DragMode_Move) { ESM::Position position = objectTag->mObject->getPosition(); for (int i=0; i<3; ++i) { position.pos[i] += offset[i]; } objectTag->mObject->setPosition(position.pos); } else if (mDragMode == DragMode_Rotate) { 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) { // Reset scale objectTag->mObject->setEdited(0); objectTag->mObject->setEdited(Object::Override_Scale); float scale = objectTag->mObject->getScale(); scale *= offset.x(); 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_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())) { objectTag->mObject->apply (macro); } } 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) { 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); for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { 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]; objectTag->mObject->setPosition (position.pos); } } } } 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.47.0/apps/opencs/view/render/instancemode.hpp000066400000000000000000000103261413061077700242670ustar00rootroot00000000000000#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; int getSubModeFromId (const std::string& id) const; osg::Vec3f quatToEuler(const osg::Quat& quat) const; osg::Quat eulerToQuat(const osg::Vec3f& euler) 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.47.0/apps/opencs/view/render/instancemovemode.cpp000066400000000000000000000006601413061077700251510ustar00rootroot00000000000000 #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
  • " "
" "Grid move not implemented yet", parent) {} openmw-openmw-0.47.0/apps/opencs/view/render/instancemovemode.hpp000066400000000000000000000004721413061077700251570ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/instanceselectionmode.cpp000066400000000000000000000361601413061077700261740ustar00rootroot00000000000000#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; int resolution = 32; float radiusPerResolution = radius / resolution; float reciprocalResolution = 1.0f / resolution; float doubleReciprocalRes = reciprocalResolution * 2; osg::Vec4Array *colours = new osg::Vec4Array; for (float i = 0.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.47.0/apps/opencs/view/render/instanceselectionmode.hpp000066400000000000000000000042371413061077700262010ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lighting.cpp000066400000000000000000000014131413061077700234130ustar00rootroot00000000000000#include "lighting.hpp" #include #include #include #include class DayNightSwitchVisitor : public osg::NodeVisitor { public: DayNightSwitchVisitor(int index) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mIndex(index) { } void apply(osg::Switch &switchNode) override { if (switchNode.getName() == Constants::NightDayLabel) switchNode.setSingleChildOn(mIndex); 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.47.0/apps/opencs/view/render/lighting.hpp000066400000000000000000000012641413061077700234240ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingbright.cpp000066400000000000000000000016761413061077700246260ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingbright.hpp000066400000000000000000000007531413061077700246260ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingday.cpp000066400000000000000000000017541413061077700241210ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingday.hpp000066400000000000000000000006521413061077700241220ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingnight.cpp000066400000000000000000000020121413061077700244410ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/lightingnight.hpp000066400000000000000000000006551413061077700244610ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/mask.hpp000066400000000000000000000014261413061077700225520ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/object.cpp000066400000000000000000000520201413061077700230540ustar00rootroot00000000000000#include "object.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../../model/world/data.hpp" #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/universalid.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 geode (new osg::Geode); geode->addDrawable(shapedrawable); return geode; } } CSVRender::ObjectTag::ObjectTag (Object* object) : TagBase (Mask_Reference), mObject (object) {} QString CSVRender::ObjectTag::getToolTip (bool hideBasics) 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.reset(new Actor(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_ParticleSystem, 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 geode (new osg::Geode); geode->addDrawable (geometry); return geode; } 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::min(64, std::max(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance))); 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 geode = new osg::Geode(); geode->addDrawable (geometry); return geode; } 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 refNumColumn = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_RefNum)); if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); commands.push (new CSMWorld::ModifyCommand (*model, model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); commands.push (new CSMWorld::ModifyCommand( *model, model->index (recordIndex, refNumColumn), 0)); } } 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.47.0/apps/opencs/view/render/object.hpp000066400000000000000000000134601413061077700230660ustar00rootroot00000000000000#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; class Geode; } 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 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.47.0/apps/opencs/view/render/orbitcameramode.cpp000066400000000000000000000027641413061077700247550ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/orbitcameramode.hpp000066400000000000000000000017011413061077700247500ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/pagedworldspacewidget.cpp000066400000000000000000000763761413061077700262020ustar00rootroot00000000000000#include "pagedworldspacewidget.hpp" #include #include #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; std::unique_ptr cell (new Cell (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; std::unique_ptr cell ( new Cell (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.47.0/apps/opencs/view/render/pagedworldspacewidget.hpp000066400000000000000000000164151413061077700261730ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/pathgrid.cpp000066400000000000000000000540701413061077700234170ustar00rootroot00000000000000#include "pathgrid.hpp" #include #include #include #include #include #include #include #include #include "../../model/world/cell.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtree.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 { QString text("Pathgrid: "); text += mPathgrid->getId().c_str(); 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); mPathgridGeode = new osg::Geode(); mBaseNode->addChild(mPathgridGeode); 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); mPathgridGeode->removeDrawable(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); mPathgridGeode->addDrawable(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); mPathgridGeode->addDrawable(mSelectedGeometry); } void Pathgrid::removePathgridGeometry() { if (mPathgridGeometry) { mPathgridGeode->removeDrawable(mPathgridGeometry); mPathgridGeometry = nullptr; } } void Pathgrid::removeSelectedGeometry() { if (mSelectedGeometry) { mPathgridGeode->removeDrawable(mSelectedGeometry); mSelectedGeometry = nullptr; } } void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) { if (mDragGeometry) mPathgridGeode->removeDrawable(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); mPathgridGeode->addDrawable(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.47.0/apps/opencs/view/render/pathgrid.hpp000066400000000000000000000077711413061077700234320ustar00rootroot00000000000000#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 Geode; 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 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 mPathgridGeode; 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.47.0/apps/opencs/view/render/pathgridmode.cpp000066400000000000000000000231161413061077700242610ustar00rootroot00000000000000#include "pathgridmode.hpp" #include #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.47.0/apps/opencs/view/render/pathgridmode.hpp000066400000000000000000000032731413061077700242700ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/pathgridselectionmode.cpp000066400000000000000000000047471413061077700262000ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/pathgridselectionmode.hpp000066400000000000000000000016671413061077700262030ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/previewwidget.cpp000066400000000000000000000111311413061077700244710ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/previewwidget.hpp000066400000000000000000000022051413061077700245000ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/scenewidget.cpp000066400000000000000000000507671413061077700241270ustar00rootroot00000000000000#include "scenewidget.hpp" #include #include #include #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 = ""; 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); #if OSG_VERSION_LESS_THAN(3,6,5) // before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released. // 3.6.4 moved it into the object cache, which meant it usually got released, but not here. // 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache. osg::ref_ptr graphicsContext = mView->getCamera()->getGraphicsContext(); osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState()); #endif } 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); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) setUseConfigureAffinity(false); #endif // 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(); } } 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.47.0/apps/opencs/view/render/scenewidget.hpp000066400000000000000000000106021413061077700241140ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/selectionmode.cpp000066400000000000000000000062771413061077700244550ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/selectionmode.hpp000066400000000000000000000023611413061077700244500ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/tagbase.cpp000066400000000000000000000003611413061077700232150ustar00rootroot00000000000000 #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 { return ""; } openmw-openmw-0.47.0/apps/opencs/view/render/tagbase.hpp000066400000000000000000000006111413061077700232200ustar00rootroot00000000000000#ifndef OPENCS_VIEW_TAGBASE_H #define OPENCS_VIEW_TAGBASE_H #include #include #include "mask.hpp" namespace CSVRender { class TagBase : public osg::Referenced { Mask mMask; public: TagBase (Mask mask); Mask getMask() const; virtual QString getToolTip (bool hideBasics) const; }; } #endif openmw-openmw-0.47.0/apps/opencs/view/render/terrainselection.cpp000066400000000000000000000344101413061077700251630ustar00rootroot00000000000000#include "terrainselection.hpp" #include #include #include #include #include #include "../../model/world/cellcoordinates.hpp" #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), mDraggedOperationFlag(false), mSelectionType(type) { mGeometry = new osg::Geometry(); mSelectionNode = new osg::Group(); 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, bool toggleInProgress) { handleSelection(localPositions, toggleInProgress, SelectionMethod::AddSelect); } void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions, bool toggleInProgress) { handleSelection(localPositions, toggleInProgress, SelectionMethod::RemoveSelect); } void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions, bool toggleInProgress) { handleSelection(localPositions, toggleInProgress, SelectionMethod::ToggleSelect); } 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) + 2); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1) + 2)); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y) + 2)); const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); if (north == mSelection.end()) { vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1) + 2)); } const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); if (east == mSelection.end()) { vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y) + 2)); } } } } 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)+2)); vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+i, y2)+2)); } } 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)+2)); vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+i, y1)+2)); } } 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))+2)); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawCurrentY, calculateLandHeight(x2, y1+i)+2)); } } 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))+2)); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawCurrentY, calculateLandHeight(x1, y1+i)+2)); } } } } } void CSVRender::TerrainSelection::handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) { if (toggleInProgress) { for (auto const& localPos : localPositions) { auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); mDraggedOperationFlag = true; if (iterTemp == mTemporarySelection.end()) { auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); switch (selectionMethod) { 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: if (iter == mSelection.end()) { mSelection.emplace_back(localPos); } else { mSelection.erase(iter); } break; default: break; } } mTemporarySelection.push_back(localPos); } } else if (mDraggedOperationFlag == false) { for (auto const& localPos : localPositions) { const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); switch (selectionMethod) { 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: if (iter == mSelection.end()) { mSelection.emplace_back(localPos); } else { mSelection.erase(iter); } break; default: break; } } } else { mDraggedOperationFlag = false; mTemporarySelection.clear(); } 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(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); return mPointer[localY*ESM::Land::LAND_SIZE + localX]; } return landHeight; } openmw-openmw-0.47.0/apps/opencs/view/render/terrainselection.hpp000066400000000000000000000053431413061077700251730ustar00rootroot00000000000000#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, bool toggleInProgress); void removeSelect(const std::vector>& localPositions, bool toggleInProgress); void toggleSelect(const std::vector> &localPositions, bool toggleInProgress); 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, bool toggleInProgress, 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 bool mDraggedOperationFlag; //true during drag operation, false when click-operation TerrainSelectionType mSelectionType; }; } #endif openmw-openmw-0.47.0/apps/opencs/view/render/terrainshapemode.cpp000066400000000000000000002245671413061077700251610ustar00rootroot00000000000000#include "terrainshapemode.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../widget/brushshapes.hpp" #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.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/land.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.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.reset(new TerrainSelection(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.reset(new BrushDraw(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(); } if (mDragMode == InteractionType_PrimarySelect) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); } if (mDragMode == InteractionType_SecondarySelect) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); } } clearTransientEdits(); } void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); } } void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); } } 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, true); return false; } 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, true); return false; } 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, true); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); } } void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) { if (mDragMode == InteractionType_PrimaryEdit) { applyTerrainEditChanges(); clearTransientEdits(); } } void CSVRender::TerrainShapeMode::dragAborted() { clearTransientEdits(); } 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.reset(new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (leftHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis.reset(new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Check for height limits on y-axis if (upHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis.reset(new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (upHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis.reset(new float(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.reset(new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (rightHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis.reset(new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Check for height limits on y-axis if (downHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis.reset(new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (downHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis.reset(new float(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, bool dragOperation) { 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, dragOperation); else if (selectAction == "Remove from selection") mTerrainShapeSelection->removeSelect(selections, dragOperation); else if (selectAction == "Invert selection") mTerrainShapeSelection->toggleSelect(selections, dragOperation); } 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) { 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-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.47.0/apps/opencs/view/render/terrainshapemode.hpp000066400000000000000000000205661413061077700251570ustar00rootroot00000000000000#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, bool dragOperation); /// 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.47.0/apps/opencs/view/render/terrainstorage.cpp000066400000000000000000000143641413061077700246500ustar00rootroot00000000000000#include "terrainstorage.hpp" #include "../../model/world/land.hpp" #include "../../model/world/landtexture.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; osg::ref_ptr land = getLand (cellX, cellY); if (land) { const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; if (data) height = getVertexHeight(data, inCellX, inCellY); } else return height; 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.47.0/apps/opencs/view/render/terrainstorage.hpp000066400000000000000000000046311413061077700246510ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/terraintexturemode.cpp000066400000000000000000000747161413061077700255600ustar00rootroot00000000000000#include "terraintexturemode.hpp" #include #include #include #include #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.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Texture)); } if (!mBrushDraw) mBrushDraw.reset(new BrushDraw(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, false); } } void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, false); } } 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, true); return false; } 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, true); return false; } 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, true); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); } } 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; } } } 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, bool dragOperation) { 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); } } } } if(selectMode == 0) mTerrainTextureSelection->onlySelect(selections); if(selectMode == 1) mTerrainTextureSelection->toggleSelect(selections, dragOperation); } 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") { 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-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.47.0/apps/opencs/view/render/terraintexturemode.hpp000066400000000000000000000111431413061077700255460ustar00rootroot00000000000000#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, bool dragOperation); /// \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.47.0/apps/opencs/view/render/unpagedworldspacewidget.cpp000066400000000000000000000245611413061077700265320ustar00rootroot00000000000000#include "unpagedworldspacewidget.hpp" #include #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.reset (new Cell (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.reset (new Cell (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.47.0/apps/opencs/view/render/unpagedworldspacewidget.hpp000066400000000000000000000101361413061077700265300ustar00rootroot00000000000000#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.47.0/apps/opencs/view/render/worldspacewidget.cpp000066400000000000000000000650601413061077700251650ustar00rootroot00000000000000#include "worldspacewidget.hpp" #include #include #include #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(); mDragging = false; 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), 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.47.0/apps/opencs/view/render/worldspacewidget.hpp000066400000000000000000000242121413061077700251640ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/000077500000000000000000000000001413061077700207645ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/tools/merge.cpp000066400000000000000000000074101413061077700225710ustar00rootroot00000000000000 #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.47.0/apps/opencs/view/tools/merge.hpp000066400000000000000000000023131413061077700225730ustar00rootroot00000000000000#ifndef CSV_TOOLS_REPORTTABLE_H #define CSV_TOOLS_REPORTTABLE_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.47.0/apps/opencs/view/tools/reportsubview.cpp000066400000000000000000000023741413061077700244160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/reportsubview.hpp000066400000000000000000000012051413061077700244130ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/reporttable.cpp000066400000000000000000000242011413061077700240120ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/reporttable.hpp000066400000000000000000000052261413061077700240250ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/searchbox.cpp000066400000000000000000000121441413061077700234500ustar00rootroot00000000000000#include "searchbox.hpp" #include #include #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.47.0/apps/opencs/view/tools/searchbox.hpp000066400000000000000000000027151413061077700234600ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/searchsubview.cpp000066400000000000000000000114241413061077700243440ustar00rootroot00000000000000#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) { 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.47.0/apps/opencs/view/tools/searchsubview.hpp000066400000000000000000000024721413061077700243540ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/subviews.cpp000066400000000000000000000010331413061077700233340ustar00rootroot00000000000000#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.47.0/apps/opencs/view/tools/subviews.hpp000066400000000000000000000003301413061077700233400ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/000077500000000000000000000000001413061077700211075ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/widget/brushshapes.hpp000066400000000000000000000003551413061077700241520ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/coloreditor.cpp000066400000000000000000000070071413061077700241440ustar00rootroot00000000000000#include "coloreditor.hpp" #include #include #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.47.0/apps/opencs/view/widget/coloreditor.hpp000066400000000000000000000026021413061077700241450ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/colorpickerpopup.cpp000066400000000000000000000050641413061077700252200ustar00rootroot00000000000000#include "colorpickerpopup.hpp" #include #include #include #include #include #include CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) : QFrame(parent) { setWindowFlags(Qt::Popup); setFrameStyle(QFrame::Box | 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.47.0/apps/opencs/view/widget/colorpickerpopup.hpp000066400000000000000000000011441413061077700252200ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/completerpopup.cpp000066400000000000000000000014341413061077700246730ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/completerpopup.hpp000066400000000000000000000004741413061077700247030ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/droplineedit.cpp000066400000000000000000000021431413061077700242750ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/droplineedit.hpp000066400000000000000000000016221413061077700243030ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/modebutton.cpp000066400000000000000000000006041413061077700237730ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/modebutton.hpp000066400000000000000000000020511413061077700237760ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/pushbutton.cpp000066400000000000000000000063111413061077700240270ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/pushbutton.hpp000066400000000000000000000032541413061077700240370ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetool.cpp000066400000000000000000000016411413061077700236100ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetool.hpp000066400000000000000000000013271413061077700236160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolbar.cpp000066400000000000000000000026101413061077700242720ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolbar.hpp000066400000000000000000000016031413061077700243000ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolmode.cpp000066400000000000000000000076441413061077700244660ustar00rootroot00000000000000#include "scenetoolmode.hpp" #include #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.47.0/apps/opencs/view/widget/scenetoolmode.hpp000066400000000000000000000044471413061077700244710ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolrun.cpp000066400000000000000000000073621413061077700243430ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolrun.hpp000066400000000000000000000027601413061077700243450ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetoolshapebrush.cpp000066400000000000000000000206661413061077700257050ustar00rootroot00000000000000#include "scenetoolshapebrush.hpp" #include #include #include #include #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.47.0/apps/opencs/view/widget/scenetoolshapebrush.hpp000066400000000000000000000065701413061077700257100ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H #define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H #include #include #include #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 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.47.0/apps/opencs/view/widget/scenetooltexturebrush.cpp000066400000000000000000000340331413061077700262760ustar00rootroot00000000000000#include "scenetooltexturebrush.hpp" #include #include #include #include #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(index).isDeleted()) { mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " "; mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); } else { newBrushTextureId = ""; 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.47.0/apps/opencs/view/widget/scenetooltexturebrush.hpp000066400000000000000000000074051413061077700263060ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H #define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H #include #include #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.47.0/apps/opencs/view/widget/scenetooltoggle.cpp000066400000000000000000000124361413061077700250160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetooltoggle.hpp000066400000000000000000000040071413061077700250160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/widget/scenetooltoggle2.cpp000066400000000000000000000076041413061077700251010ustar00rootroot00000000000000#include "scenetooltoggle2.hpp" #include #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.47.0/apps/opencs/view/widget/scenetooltoggle2.hpp000066400000000000000000000043751413061077700251100ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/000077500000000000000000000000001413061077700207535ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/opencs/view/world/bodypartcreator.cpp000066400000000000000000000021241413061077700246620ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/bodypartcreator.hpp000066400000000000000000000016601413061077700246730ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/cellcreator.cpp000066400000000000000000000063571413061077700237710ustar00rootroot00000000000000#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); 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.47.0/apps/opencs/view/world/cellcreator.hpp000066400000000000000000000023361413061077700237670ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/colordelegate.cpp000066400000000000000000000026341413061077700242750ustar00rootroot00000000000000#include "colordelegate.hpp" #include #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.47.0/apps/opencs/view/world/colordelegate.hpp000066400000000000000000000020341413061077700242740ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/creator.cpp000066400000000000000000000010021413061077700231070ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/creator.hpp000066400000000000000000000062651413061077700231340ustar00rootroot00000000000000#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 { std::unique_ptr creator (new CreatorT (document.getData(), document.getUndoStack(), id)); creator->setScope (scope); return creator.release(); } } #endif openmw-openmw-0.47.0/apps/opencs/view/world/datadisplaydelegate.cpp000066400000000000000000000123321413061077700254520ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/datadisplaydelegate.hpp000077500000000000000000000052331413061077700254640ustar00rootroot00000000000000#ifndef DATADISPLAYDELEGATE_HPP #define DATADISPLAYDELEGATE_HPP #include #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.47.0/apps/opencs/view/world/dialoguecreator.cpp000066400000000000000000000025661413061077700246410ustar00rootroot00000000000000#include "dialoguecreator.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" 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.47.0/apps/opencs/view/world/dialoguecreator.hpp000066400000000000000000000020411413061077700246320ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dialoguespinbox.cpp000066400000000000000000000023311413061077700246520ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dialoguespinbox.hpp000066400000000000000000000016031413061077700246600ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dialoguesubview.cpp000066400000000000000000001021631413061077700246600ustar00rootroot00000000000000#include "dialoguesubview.hpp" #include #include #include #include #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.reset(new refWrapper(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 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(); 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); if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) { 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 { 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); } createEditorContextMenu(editor, display, row); } } 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.47.0/apps/opencs/view/world/dialoguesubview.hpp000066400000000000000000000211741413061077700246670ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dragdroputils.cpp000066400000000000000000000023441413061077700243450ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dragdroputils.hpp000066400000000000000000000017011413061077700243460ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dragrecordtable.cpp000066400000000000000000000057051413061077700246120ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/dragrecordtable.hpp000066400000000000000000000022311413061077700246060ustar00rootroot00000000000000#ifndef CSV_WORLD_DRAGRECORDTABLE_H #define CSV_WORLD_DRAGRECORDTABLE_H #include #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.47.0/apps/opencs/view/world/enumdelegate.cpp000066400000000000000000000130171413061077700241200ustar00rootroot00000000000000#include "enumdelegate.hpp" #include #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); 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.47.0/apps/opencs/view/world/enumdelegate.hpp000066400000000000000000000055011413061077700241240ustar00rootroot00000000000000#ifndef CSV_WORLD_ENUMDELEGATE_H #define CSV_WORLD_ENUMDELEGATE_H #include #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.47.0/apps/opencs/view/world/extendedcommandconfigurator.cpp000066400000000000000000000161221413061077700272430ustar00rootroot00000000000000#include "extendedcommandconfigurator.hpp" #include #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.47.0/apps/opencs/view/world/extendedcommandconfigurator.hpp000066400000000000000000000035731413061077700272560ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/genericcreator.cpp000066400000000000000000000223261413061077700244600ustar00rootroot00000000000000#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::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.reset (new CSMWorld::CloneCommand ( dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType)); } else { command.reset (new CSMWorld::CreateCommand ( 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.47.0/apps/opencs/view/world/genericcreator.hpp000066400000000000000000000072171413061077700244670ustar00rootroot00000000000000#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; 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.47.0/apps/opencs/view/world/globalcreator.cpp000066400000000000000000000014471413061077700243050ustar00rootroot00000000000000#include "globalcreator.hpp" #include #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.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.47.0/apps/opencs/view/world/globalcreator.hpp000066400000000000000000000006771413061077700243160ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/idcompletiondelegate.cpp000066400000000000000000000103011413061077700256330ustar00rootroot00000000000000#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()); 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.47.0/apps/opencs/view/world/idcompletiondelegate.hpp000066400000000000000000000026151413061077700256510ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/idtypedelegate.cpp000077500000000000000000000020221413061077700244470ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/idtypedelegate.hpp000077500000000000000000000015361413061077700244650ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/idvalidator.cpp000066400000000000000000000060421413061077700237630ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/idvalidator.hpp000066400000000000000000000020201413061077700237600ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/infocreator.cpp000066400000000000000000000115341413061077700237760ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/infocreator.hpp000066400000000000000000000027651413061077700240110ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/landcreator.cpp000066400000000000000000000072361413061077700237650ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/landcreator.hpp000066400000000000000000000020301413061077700237550ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/landtexturecreator.cpp000066400000000000000000000060771413061077700254100ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/landtexturecreator.hpp000066400000000000000000000020121413061077700253760ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/nestedtable.cpp000066400000000000000000000115041413061077700237520ustar00rootroot00000000000000#include "nestedtable.hpp" #include #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.47.0/apps/opencs/view/world/nestedtable.hpp000066400000000000000000000025031413061077700237560ustar00rootroot00000000000000#ifndef CSV_WORLD_NESTEDTABLE_H #define CSV_WORLD_NESTEDTABLE_H #include #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.47.0/apps/opencs/view/world/pathgridcreator.cpp000066400000000000000000000057101413061077700246440ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/pathgridcreator.hpp000066400000000000000000000037511413061077700246540ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/previewsubview.cpp000066400000000000000000000037511413061077700245530ustar00rootroot00000000000000#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); 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.47.0/apps/opencs/view/world/previewsubview.hpp000066400000000000000000000012641413061077700245550ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/recordbuttonbar.cpp000066400000000000000000000145661413061077700246720ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/recordbuttonbar.hpp000066400000000000000000000037321413061077700246700ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/recordstatusdelegate.cpp000066400000000000000000000025201413061077700256730ustar00rootroot00000000000000#include "recordstatusdelegate.hpp" #include #include #include #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.47.0/apps/opencs/view/world/recordstatusdelegate.hpp000066400000000000000000000016771413061077700257140ustar00rootroot00000000000000#ifndef RECORDSTATUSDELEGATE_H #define RECORDSTATUSDELEGATE_H #include "util.hpp" #include #include #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.47.0/apps/opencs/view/world/referenceablecreator.cpp000066400000000000000000000031771413061077700256310ustar00rootroot00000000000000#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); 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())); } insertBeforeButtons (mType, false); } void CSVWorld::ReferenceableCreator::reset() { mType->setCurrentIndex (0); GenericCreator::reset(); } 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.47.0/apps/opencs/view/world/referenceablecreator.hpp000066400000000000000000000013731413061077700256320ustar00rootroot00000000000000#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; }; } #endif openmw-openmw-0.47.0/apps/opencs/view/world/referencecreator.cpp000066400000000000000000000066511413061077700250050ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/referencecreator.hpp000066400000000000000000000030341413061077700250020ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/regionmap.cpp000066400000000000000000000277301413061077700234510ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/regionmap.hpp000066400000000000000000000042211413061077700234440ustar00rootroot00000000000000#ifndef CSV_WORLD_REGIONMAP_H #define CSV_WORLD_REGIONMAP_H #include #include #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.47.0/apps/opencs/view/world/regionmapsubview.cpp000066400000000000000000000013231413061077700250440ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/regionmapsubview.hpp000066400000000000000000000011531413061077700250520ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scenesubview.cpp000066400000000000000000000174121413061077700241660ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scenesubview.hpp000066400000000000000000000037601413061077700241740ustar00rootroot00000000000000#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 Table; 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.47.0/apps/opencs/view/world/scriptedit.cpp000066400000000000000000000346701413061077700236430ustar00rootroot00000000000000#include "scriptedit.hpp" #include #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.47.0/apps/opencs/view/world/scriptedit.hpp000066400000000000000000000067051413061077700236460ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scripterrortable.cpp000066400000000000000000000115051413061077700250470ustar00rootroot00000000000000#include "scripterrortable.hpp" #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.47.0/apps/opencs/view/world/scripterrortable.hpp000066400000000000000000000032361413061077700250560ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scripthighlighter.cpp000066400000000000000000000111511413061077700252010ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scripthighlighter.hpp000066400000000000000000000061361413061077700252150ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/scriptsubview.cpp000066400000000000000000000253731413061077700244020ustar00rootroot00000000000000#include "scriptsubview.hpp" #include #include #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.47.0/apps/opencs/view/world/scriptsubview.hpp000066400000000000000000000041211413061077700243730ustar00rootroot00000000000000#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 QVBoxLayout; 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.47.0/apps/opencs/view/world/startscriptcreator.cpp000066400000000000000000000061441413061077700254260ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/startscriptcreator.hpp000066400000000000000000000037201413061077700254300ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/subviews.cpp000066400000000000000000000226771413061077700233440ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/subviews.hpp000066400000000000000000000003301413061077700233270ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/table.cpp000066400000000000000000000735571413061077700225670ustar00rootroot00000000000000#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/idtableproxymodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexturetableproxymodel.hpp" #include "../../model/world/record.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" #include "tableeditidaction.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) { 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); setSortingEnabled (sorting); if (sorting) { sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); } 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); } 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 (tableSizeUpdate())); 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(); } 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; } void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); selectRow(mProxyModel->getModelIndex(id, idColumn).row()); if(mUnselectAfterJump) clearSelection(); } } openmw-openmw-0.47.0/apps/opencs/view/world/table.hpp000066400000000000000000000106161413061077700225570ustar00rootroot00000000000000#ifndef CSV_WORLD_TABLE_H #define CSV_WORLD_TABLE_H #include #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; 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); }; } #endif openmw-openmw-0.47.0/apps/opencs/view/world/tablebottombox.cpp000066400000000000000000000156551413061077700245200ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/tablebottombox.hpp000066400000000000000000000063121413061077700245130ustar00rootroot00000000000000#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); virtual ~TableBottomBox(); 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.47.0/apps/opencs/view/world/tableeditidaction.cpp000066400000000000000000000032321413061077700251270ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/tableeditidaction.hpp000066400000000000000000000014231413061077700251340ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/tablesubview.cpp000066400000000000000000000143141413061077700241560ustar00rootroot00000000000000#include "tablesubview.hpp" #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) { 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); layout->insertWidget (0, mFilterBox); 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::requestFocus (const std::string& id) { mTable->requestFocus(id); } openmw-openmw-0.47.0/apps/opencs/view/world/tablesubview.hpp000066400000000000000000000030251413061077700241600ustar00rootroot00000000000000#ifndef CSV_WORLD_TABLESUBVIEW_H #define CSV_WORLD_TABLESUBVIEW_H #include "../doc/subview.hpp" #include class QModelIndex; 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; 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); public slots: void requestFocus (const std::string& id); }; } #endif openmw-openmw-0.47.0/apps/opencs/view/world/util.cpp000066400000000000000000000264161413061077700224450ustar00rootroot00000000000000#include "util.hpp" #include #include #include #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; } 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.47.0/apps/opencs/view/world/util.hpp000066400000000000000000000117261413061077700224500ustar00rootroot00000000000000#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.47.0/apps/opencs/view/world/vartypedelegate.cpp000066400000000000000000000044521413061077700246510ustar00rootroot00000000000000#include "vartypedelegate.hpp" #include #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.47.0/apps/opencs/view/world/vartypedelegate.hpp000066400000000000000000000023461413061077700246560ustar00rootroot00000000000000#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.47.0/apps/openmw/000077500000000000000000000000001413061077700166705ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/CMakeLists.txt000066400000000000000000000215251413061077700214350ustar00rootroot00000000000000# local files set(GAME main.cpp engine.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 npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover ) add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch inputmanagerimp mousemanager keyboardmanager sdlmappings sensormanager ) 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 ) 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 (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 recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager cellpreloader datetimemanager ) 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 actor summoning character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects spellabsorption linkedeffects ) 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_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} ${FFmpeg_LIBRARIES} ${MyGUI_LIBRARIES} ${SDL2_LIBRARY} ${RecastNavigation_LIBRARIES} "osg-ffmpeg-videoplayer" "oics" components ) if(OSG_STATIC) unset(_osg_plugins_static_files) add_library(openmw_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(openmw_osg_plugins INTERFACE $) add_dependencies(openmw_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(openmw_osg_plugins INTERFACE ${_opts}) target_link_libraries(openmw openmw_osg_plugins) if(OPENMW_USE_SYSTEM_OSG) # OSG plugin pkgconfig files are missing these dependencies. # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 target_link_libraries(openmw freetype jpeg png) endif() endif(OSG_STATIC) 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_MYGUI_FILES_ROOT ${BUNDLE_RESOURCES_DIR}) set(OPENMW_SHADERS_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) find_library(COREVIDEO_FRAMEWORK CoreVideo) find_library(VDA_FRAMEWORK VideoDecodeAcceleration) target_link_libraries(openmw z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK}) endif() endif(APPLE) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) 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 (WIN32) INSTALL(TARGETS openmw RUNTIME DESTINATION ".") endif (WIN32) openmw-openmw-0.47.0/apps/openmw/android_main.cpp000066400000000000000000000041161413061077700220220ustar00rootroot00000000000000int stderr = 0; // Hack: fix linker error #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.47.0/apps/openmw/doc.hpp000066400000000000000000000016441413061077700201530ustar00rootroot00000000000000// 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.47.0/apps/openmw/engine.cpp000066400000000000000000001052731413061077700206510ustar00rootroot00000000000000#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 "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.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, 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 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"); } } void OMW::Engine::executeLocalScripts() { MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); std::pair script; while (localScripts.getNext(script)) { MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); mEnvironment.getScriptManager()->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); mEnvironment.getInputManager()->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 (!mEnvironment.getWindowManager()->isWindowVisible()) { mEnvironment.getSoundManager()->pausePlayback(); return false; } else mEnvironment.getSoundManager()->resumePlayback(); // sound if (mUseSound) mEnvironment.getSoundManager()->update(frametime); } // Main menu opened? Then scripts are also paused. bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mEnvironment.getStateManager()->update (frametime); } bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { if (mEnvironment.getWorld()->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts mEnvironment.getScriptManager()->getGlobalScripts().run(); } mEnvironment.getWorld()->markCellAsUnchanged(); } if (!guiActive) { double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; mEnvironment.getWorld()->advanceTime(hours, true); mEnvironment.getWorld()->rechargeItems(frametime, true); } } } // update mechanics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getMechanicsManager()->update(frametime, guiActive); } if (mEnvironment.getStateManager()->getState() == MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); if(!guiActive && player.getClass().getCreatureStats(player).isDead()) mEnvironment.getStateManager()->endGame(); } } // update physics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getWorld()->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } // update world { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getWorld()->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mEnvironment.getWindowManager()->update(frametime); } if (stats->collectStats("resource")) { stats->setAttribute(frameNumber, "FrameNumber", frameNumber); mResourceSystem->reportStats(frameNumber, stats); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); mEnvironment.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) , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) , mCompileAllDialogue (false) , mWarningsMode (1) , mScriptConsoleMode (false) , mActivationDistanceOverride(-1) , mGrab(true) , mExportFonts(false) , mRandomSeed(0) , mScriptContext (nullptr) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) , mCfgMgr(configurationManager) { 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() { mEnvironment.cleanup(); delete mScriptContext; mScriptContext = nullptr; mWorkQueue = nullptr; mViewer = nullptr; mResourceSystem.reset(); delete mEncoder; 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; } std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); // prefer local if (boost::filesystem::exists(localdefault)) settings.loadDefault(localdefault); else if (boost::filesystem::exists(globaldefault)) settings.loadDefault(globaldefault); else throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); // load user settings if they exist const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); return settingspath; } void OMW::Engine::createWindow(Settings::Manager& settings) { int screen = settings.getInt("screen", "Video"); int width = settings.getInt("resolution x", "Video"); int height = settings.getInt("resolution y", "Video"); bool fullscreen = settings.getBool("fullscreen", "Video"); bool windowBorder = settings.getBool("window border", "Video"); bool vsync = settings.getBool("vsync", "Video"); unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video")); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); if(fullscreen) { 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(fullscreen) flags |= SDL_WINDOW_FULLSCREEN; if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, settings.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); if (Debug::shouldDebugOpenGL()) mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation()); mViewer->realize(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } void OMW::Engine::setWindowIcon() { boost::filesystem::ifstream windowIconStream; std::string windowIcon = (mResDir / "mygui" / "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 (Settings::Manager & settings) { mEnvironment.setStateManager ( new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); createWindow(settings); osg::ref_ptr rootNode (new osg::Group); mViewer->setSceneData(rootNode); mVFS.reset(new VFS::Manager(mFSStrict)); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); 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") ); 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); // 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; } else userGameControllerdb = ""; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; else if (boost::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; else gameControllerdb = ""; //if it doesn't exist, pass in an empty string std::string myguiResources = (mResDir / "mygui").string(); osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); rootNode->addChild(guiRoot); MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string()); mEnvironment.setWindowManager (window); MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); mEnvironment.setInputManager (input); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) window->playVideo(logo, true); } // Create the world mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); window->setStore(mEnvironment.getWorld()->getStore()); window->initUI(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); for (size_t i = 0; i < mContentFiles.size(); i++) mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); Compiler::registerExtensions (mExtensions); // Create script system mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector())); // Create game mechanics system MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; mEnvironment.setMechanicsManager (mechanics); // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); mEnvironment.setResourceSystem(mResourceSystem.get()); // scripts if (mCompileAll) { std::pair result = mEnvironment.getScriptManager()->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 << "%)"; } } class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation { public: WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat) : mScreenshotPath(screenshotPath) , mScreenshotFormat(screenshotFormat) { } void operator()(const osg::Image& image, const unsigned int context_id) override { // Count screenshots. int shotCount = 0; // Find the first unused filename with a do-while std::ostringstream stream; do { // Reset the stream stream.str(""); stream.clear(); stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat; } while (boost::filesystem::exists(stream.str())); boost::filesystem::ofstream outStream; outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); if (!readerwriter) { Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found"; return; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); } } private: std::string mScreenshotPath; std::string mScreenshotFormat; }; // 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); // Load settings Settings::Manager settings; std::string settingspath; settingspath = loadSettings (settings); MWClass::registerClasses(); // Create encoder mEncoder = new ToUTF8::Utf8Encoder(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; mViewer->setReleaseContextAtEndOfFrameHint(false); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); #endif mScreenCaptureOperation = new WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General")); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); prepareEngine (settings); std::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()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); if (stats.is_open()) Resource::CollectStatistics(mViewer); // Start the game if (!mSaveGameFile.empty()) { mEnvironment.getStateManager()->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); mEnvironment.getSoundManager()->playTitleMusic(); const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) mEnvironment.getWindowManager()->playVideo(logo, true); } else { mEnvironment.getStateManager()->newGame (!mNewGame); } if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running) { mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } // 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() && !mEnvironment.getStateManager()->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(std::min( frameRateLimiter.getLastFrameDuration(), maxSimulationInterval )).count(); mViewer->advance(simulationTime); if (!frame(dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } else { mViewer->eventTraversal(); mViewer->updateTraversal(); mEnvironment.getWorld()->updateWindowManager(); mViewer->renderingTraversals(); bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); if (!guiActive) simulationTime += dt; } if (stats) { const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (frameNumber >= 2) { mViewer->getViewerStats()->report(stats, frameNumber - 2); osgViewer::Viewer::Cameras cameras; mViewer->getCameras(cameras); for (auto camera : cameras) camera->getStats()->report(stats, frameNumber - 2); } } frameRateLimiter.limit(); } // Save user settings settings.saveUser(settingspath); mViewer->stopThreading(); 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::enableFontExport(bool exportFonts) { mExportFonts = exportFonts; } void OMW::Engine::setSaveGameFile(const std::string &savegame) { mSaveGameFile = savegame; } void OMW::Engine::setRandomSeed(unsigned int seed) { mRandomSeed = seed; } openmw-openmw-0.47.0/apps/openmw/engine.hpp000066400000000000000000000133371413061077700206550ustar00rootroot00000000000000#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; } namespace VFS { class Manager; } namespace Compiler { class Context; } namespace Files { struct ConfigurationManager; } namespace osgViewer { class ScreenCaptureHandler; } 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; MWBase::Environment mEnvironment; ToUTF8::FromType mEncoding; ToUTF8::Utf8Encoder* mEncoder; Files::PathContainer mDataDirs; std::vector mArchives; boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; 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; bool mExportFonts; unsigned int mRandomSeed; Compiler::Extensions mExtensions; Compiler::Context *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); /// Load settings from various files, returns the path to the user settings file std::string loadSettings (Settings::Manager & settings); /// Prepare engine for game play void prepareEngine (Settings::Manager & settings); void createWindow(Settings::Manager& settings); 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); void enableFontExport(bool exportFonts); /// 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; }; } #endif /* ENGINE_H */ openmw-openmw-0.47.0/apps/openmw/main.cpp000066400000000000000000000320701413061077700203220ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "engine.hpp" #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include // makes __argc and __argv available on windows #include #endif #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) #include #endif using namespace Fallback; /** * \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("Syntax: openmw \nAllowed options"); desc.add_options() ("help", "print help message") ("version", "print version information and quit") ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), "set resources directory") ("start", bpo::value()->default_value(""), "set initial cell") ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") ->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(Files::EscapeStringVector(), "") ->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::EscapePath(), ""), "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(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") ; bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); bpo::variables_map variables; // Runtime options override settings from all configs bpo::store(valid_opts, variables); 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().mPath.string()); getRawStdout() << v.describe() << std::endl; return false; } bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); cfgMgr.mergeComposingVariables(variables, composingVariables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings std::string encoding(variables["encoding"].as().toStdString()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings engine.enableFSStrict(variables["fs-strict"].as()); Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as())); Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) dataDirs.push_back(local); cfgMgr.processPaths(dataDirs); engine.setResourceDir(variables["resources"].as().mPath); engine.setDataDirs(dataDirs); // fallback archives StringsVector archives = variables["fallback-archive"].as().toStdStringVector(); for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it) { engine.addArchive(*it); } StringsVector content = variables["content"].as().toStdStringVector(); 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().toStdStringVector(); for (auto& file : groundcover) { engine.addGroundcoverFile(file); } // startup-settings engine.setCell(variables["start"].as().toStdString()); 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().toStdString()); engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as().toStdStringVector()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); engine.setSaveGameFile (variables["load-savegame"].as().mPath.string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); engine.setSoundUsage(!variables["no-sound"].as()); engine.setActivationDistanceOverride (variables["activate-dist"].as()); engine.enableFontExport(variables["export-fonts"].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[]) { #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; engine.reset(new OMW::Engine(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, "OpenMW"); } // 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.47.0/apps/openmw/mwbase/000077500000000000000000000000001413061077700201465ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwbase/dialoguemanager.hpp000066400000000000000000000072361413061077700240130ustar00rootroot00000000000000#ifndef GAME_MWBASE_DIALOGUEMANAGER_H #define GAME_MWBASE_DIALOGUEMANAGER_H #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) = 0; virtual void addTopic (const std::string& topic) = 0; virtual void addChoice (const std::string& text,int choice) = 0; virtual const std::vector >& getChoices() = 0; virtual bool isGoodbye() = 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&) = 0; virtual bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) = 0; virtual void persuade (int type, ResponseCallback* callback) = 0; virtual int getTemporaryDispositionChange () const = 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 (const std::string& faction1, const std::string& faction2, int diff) = 0; virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) = 0; /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& 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.47.0/apps/openmw/mwbase/environment.cpp000066400000000000000000000104411413061077700232160ustar00rootroot00000000000000#include "environment.hpp" #include #include #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" #include "journal.hpp" #include "soundmanager.hpp" #include "mechanicsmanager.hpp" #include "inputmanager.hpp" #include "windowmanager.hpp" #include "statemanager.hpp" MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() : mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; } MWBase::Environment::~Environment() { cleanup(); sThis = nullptr; } void MWBase::Environment::setWorld (World *world) { mWorld = world; } void MWBase::Environment::setSoundManager (SoundManager *soundManager) { mSoundManager = soundManager; } void MWBase::Environment::setScriptManager (ScriptManager *scriptManager) { mScriptManager = scriptManager; } void MWBase::Environment::setWindowManager (WindowManager *windowManager) { mWindowManager = windowManager; } void MWBase::Environment::setMechanicsManager (MechanicsManager *mechanicsManager) { mMechanicsManager = mechanicsManager; } void MWBase::Environment::setDialogueManager (DialogueManager *dialogueManager) { mDialogueManager = dialogueManager; } void MWBase::Environment::setJournal (Journal *journal) { mJournal = journal; } void MWBase::Environment::setInputManager (InputManager *inputManager) { mInputManager = inputManager; } void MWBase::Environment::setStateManager (StateManager *stateManager) { mStateManager = stateManager; } void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) { mResourceSystem = resourceSystem; } void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; } void MWBase::Environment::setFrameRateLimit(float limit) { mFrameRateLimit = limit; } float MWBase::Environment::getFrameRateLimit() const { return mFrameRateLimit; } MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); return mWorld; } MWBase::SoundManager *MWBase::Environment::getSoundManager() const { assert (mSoundManager); return mSoundManager; } MWBase::ScriptManager *MWBase::Environment::getScriptManager() const { assert (mScriptManager); return mScriptManager; } MWBase::WindowManager *MWBase::Environment::getWindowManager() const { assert (mWindowManager); return mWindowManager; } MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const { assert (mMechanicsManager); return mMechanicsManager; } MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const { assert (mDialogueManager); return mDialogueManager; } MWBase::Journal *MWBase::Environment::getJournal() const { assert (mJournal); return mJournal; } MWBase::InputManager *MWBase::Environment::getInputManager() const { assert (mInputManager); return mInputManager; } MWBase::StateManager *MWBase::Environment::getStateManager() const { assert (mStateManager); return mStateManager; } Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const { return mResourceSystem; } float MWBase::Environment::getFrameDuration() const { return mFrameDuration; } void MWBase::Environment::cleanup() { delete mMechanicsManager; mMechanicsManager = nullptr; delete mDialogueManager; mDialogueManager = nullptr; delete mJournal; mJournal = nullptr; delete mScriptManager; mScriptManager = nullptr; delete mWindowManager; mWindowManager = nullptr; delete mWorld; mWorld = nullptr; delete mSoundManager; mSoundManager = nullptr; delete mInputManager; mInputManager = nullptr; delete mStateManager; mStateManager = nullptr; } const MWBase::Environment& MWBase::Environment::get() { assert (sThis); return *sThis; } void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mMechanicsManager->reportStats(frameNumber, stats); mWorld->reportStats(frameNumber, stats); } openmw-openmw-0.47.0/apps/openmw/mwbase/environment.hpp000066400000000000000000000061561413061077700232330ustar00rootroot00000000000000#ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H namespace osg { class Stats; } namespace Resource { class ResourceSystem; } namespace MWBase { class World; class ScriptManager; class DialogueManager; class Journal; class SoundManager; class MechanicsManager; class InputManager; class WindowManager; class StateManager; /// \brief Central hub for mw-subsystems /// /// This class allows each mw-subsystem to access any others subsystem's top-level manager class. /// /// \attention Environment takes ownership of the manager class instances it is handed over in /// the set* functions. class Environment { static Environment *sThis; World *mWorld; SoundManager *mSoundManager; ScriptManager *mScriptManager; WindowManager *mWindowManager; MechanicsManager *mMechanicsManager; DialogueManager *mDialogueManager; Journal *mJournal; InputManager *mInputManager; StateManager *mStateManager; Resource::ResourceSystem *mResourceSystem; float mFrameDuration; float mFrameRateLimit; Environment (const Environment&); ///< not implemented Environment& operator= (const Environment&); ///< not implemented public: Environment(); ~Environment(); void setWorld (World *world); void setSoundManager (SoundManager *soundManager); void setScriptManager (MWBase::ScriptManager *scriptManager); void setWindowManager (WindowManager *windowManager); void setMechanicsManager (MechanicsManager *mechanicsManager); void setDialogueManager (DialogueManager *dialogueManager); void setJournal (Journal *journal); void setInputManager (InputManager *inputManager); void setStateManager (StateManager *stateManager); void setResourceSystem (Resource::ResourceSystem *resourceSystem); void setFrameDuration (float duration); ///< Set length of current frame in seconds. void setFrameRateLimit(float frameRateLimit); float getFrameRateLimit() const; World *getWorld() const; SoundManager *getSoundManager() const; ScriptManager *getScriptManager() const; WindowManager *getWindowManager() const; MechanicsManager *getMechanicsManager() const; DialogueManager *getDialogueManager() const; Journal *getJournal() const; InputManager *getInputManager() const; StateManager *getStateManager() const; Resource::ResourceSystem *getResourceSystem() const; float getFrameDuration() const; void cleanup(); ///< Delete all mw*-subsystems. static const Environment& get(); ///< Return instance of this class. void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/inputmanager.hpp000066400000000000000000000054761413061077700233650ustar00rootroot00000000000000#ifndef GAME_MWBASE_INPUTMANAGER_H #define GAME_MWBASE_INPUTMANAGER_H #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 (const std::string& sw, bool value) = 0; virtual bool getControlSwitch (const std::string& sw) = 0; virtual std::string getActionDescription (int action) = 0; virtual std::string getActionKeyBindingName (int action) = 0; virtual std::string getActionControllerBindingName (int action) = 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 void executeAction(int action) = 0; virtual bool controlsDisabled() = 0; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/journal.hpp000066400000000000000000000064051413061077700223360ustar00rootroot00000000000000#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.47.0/apps/openmw/mwbase/mechanicsmanager.hpp000066400000000000000000000324431413061077700241520ustar00rootroot00000000000000#ifndef GAME_MWBASE_MECHANICSMANAGER_H #define GAME_MWBASE_MECHANICSMANAGER_H #include #include #include #include #include #include "../mwmechanics/actorutil.hpp" // For MWMechanics::GreetingState #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) = 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 update (float duration, bool paused) = 0; ///< Update objects /// /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). 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(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 addTemporaryDispositionChange = 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; 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, float& tempChange, float& 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::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list 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::list getActorsFighting(const MWWorld::Ptr& actor) = 0; virtual std::list 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; virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/rotationflags.hpp000066400000000000000000000004601413061077700235330ustar00rootroot00000000000000#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.47.0/apps/openmw/mwbase/scriptmanager.hpp000066400000000000000000000025141413061077700235200ustar00rootroot00000000000000#ifndef GAME_MWBASE_SCRIPTMANAGER_H #define GAME_MWBASE_SCRIPTMANAGER_H #include namespace Interpreter { class Context; } namespace Compiler { 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/soundmanager.hpp000066400000000000000000000206301413061077700233430ustar00rootroot00000000000000#ifndef GAME_MWBASE_SOUNDMANAGER_H #define GAME_MWBASE_SOUNDMANAGER_H #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. */ 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; 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(const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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 update(float duration) = 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; virtual void clear() = 0; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/statemanager.hpp000066400000000000000000000055421413061077700233400ustar00rootroot00000000000000#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 endGame() = 0; 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; virtual void update (float duration) = 0; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/windowmanager.hpp000066400000000000000000000322061413061077700235240ustar00rootroot00000000000000#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; 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) = 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 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; /// 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; /// 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() = 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; 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 update (float duration) = 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 void loadUserFonts() = 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; /// 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; // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path) = 0; virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0; virtual std::string correctTexturePath(const std::string& path) = 0; virtual bool textureExists(const std::string& path) = 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwbase/world.hpp000066400000000000000000000746521413061077700220240ustar00rootroot00000000000000#ifndef GAME_MWBASE_WORLD_H #define GAME_MWBASE_WORLD_H #include "rotationflags.hpp" #include #include #include #include #include #include #include "../mwworld/ptr.hpp" #include "../mwworld/doorstate.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 RayCastingInterface; } namespace MWRender { class Animation; } namespace MWMechanics { struct Movement; } namespace DetourNavigator { struct Navigator; } 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 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 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 void adjustSky() = 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 std::vector& getEsmReader() = 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 (const std::string& name, int value) = 0; ///< Set value independently from real type. virtual void setGlobalFloat (const std::string& name, float value) = 0; ///< Set value independently from real type. virtual int getGlobalInt (const std::string& name) const = 0; ///< Get value independently from real type. virtual float getGlobalFloat (const std::string& name) const = 0; ///< Get value independently from real type. virtual char getGlobalVariableType (const std::string& 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 (const std::string& 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 (const std::string& 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 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 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() = 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, float x, float y, float z, 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, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, RotationFlags flags = RotationFlag_inverseOrder) = 0; virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, 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 positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; ///< Convert position to cell numbers 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 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 void update (float duration, bool paused) = 0; virtual void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) = 0; virtual void updateWindowManager () = 0; 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 void togglePOV(bool force = false) = 0; virtual bool isFirstPerson() const = 0; virtual bool isPreviewModeEnabled() const = 0; virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual void allowVanityMode(bool allow) = 0; virtual bool vanityRotateCamera(float * rot) = 0; virtual void adjustCameraDistance(float dist) = 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 true if the spell can be casted (i.e. the animation should start) */ virtual bool 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) = 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) = 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) = 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 rechargeItems(double duration, bool activeOnly) = 0; virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; virtual void rotateWorldObject (const MWWorld::Ptr& ptr, 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 osg::Vec3f& halfExtents, 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; /// Return physical half extents of the given actor to be used in pathfinding virtual osg::Vec3f getPathfindingHalfExtents(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 MWWorld::ConstPtr& ignore) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; virtual std::vector getAll(const std::string& id) = 0; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/000077500000000000000000000000001413061077700203415ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwclass/activator.cpp000066400000000000000000000160151413061077700230440ustar00rootroot00000000000000#include "activator.hpp" #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" namespace MWClass { 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, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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; } void Activator::registerSelf() { std::shared_ptr instance (new Activator); registerClass (typeid (ESM::Activator).name(), instance); } bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const { return !getName(ptr).empty(); } bool Activator::allowTelekinesis(const MWWorld::ConstPtr &ptr) const { return false; } 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::shared_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(); const ESM::Sound *sound = store.get().searchRandom("WolfActivator"); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } return std::shared_ptr(new MWWorld::NullAction); } 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; for (const ESM::Creature &iter : store.get()) { if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel)) { creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId; break; } } int type = getSndGenTypeFromName(name); std::vector fallbacksounds; 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())]->mSound; if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->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())]->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.47.0/apps/openmw/mwclass/activator.hpp000066400000000000000000000041721413061077700230520ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ACTIVATOR_H #define GAME_MWCLASS_ACTIVATOR_H #include "../mwworld/class.hpp" namespace MWClass { class Activator : public MWWorld::Class { 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, 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) bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const override; ///< Return whether this class of object can be activated with telekinesis 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::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation static void registerSelf(); 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.47.0/apps/openmw/mwclass/actor.cpp000066400000000000000000000067131413061077700221640ustar00rootroot00000000000000#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 { Actor::Actor() {} Actor::~Actor() {} 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, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { 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.47.0/apps/openmw/mwclass/actor.hpp000066400000000000000000000032101413061077700221560ustar00rootroot00000000000000#ifndef GAME_MWCLASS_MOBILE_H #define GAME_MWCLASS_MOBILE_H #include "../mwworld/class.hpp" namespace ESM { struct GameSetting; } namespace MWClass { /// \brief Class holding functionality common to Creature and NPC class Actor : public MWWorld::Class { protected: Actor(); public: virtual ~Actor(); 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, 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&); Actor& operator= (const Actor&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/apparatus.cpp000066400000000000000000000107771413061077700230610ustar00rootroot00000000000000#include "apparatus.hpp" #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" namespace MWClass { void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Apparatus::registerSelf() { std::shared_ptr instance (new Apparatus); registerClass (typeid (ESM::Apparatus).name(), instance); } 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::shared_ptr Apparatus::use (const MWWorld::Ptr& ptr, bool force) const { return std::shared_ptr(new MWWorld::ActionAlchemy(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.47.0/apps/openmw/mwclass/apparatus.hpp000066400000000000000000000045351413061077700230610ustar00rootroot00000000000000#ifndef GAME_MWCLASS_APPARATUS_H #define GAME_MWCLASS_APPARATUS_H #include "../mwworld/class.hpp" namespace MWClass { class Apparatus : public MWWorld::Class { 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 void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/armor.cpp000066400000000000000000000331261413061077700221720ustar00rootroot00000000000000#include "armor.hpp" #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" namespace MWClass { void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Armor::registerSelf() { std::shared_ptr instance (new Armor); registerClass (typeid (ESM::Armor).name(), instance); } 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) typeText = ""; 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=""; 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->getTypeName() == typeid(ESM::Weapon).name()) { 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::shared_ptr Armor::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/armor.hpp000066400000000000000000000103001413061077700221640ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ARMOR_H #define GAME_MWCLASS_ARMOR_H #include "../mwworld/class.hpp" namespace MWClass { class Armor : public MWWorld::Class { 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 void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/bodypart.cpp000066400000000000000000000030061413061077700226700ustar00rootroot00000000000000#include "bodypart.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" #include "../mwworld/cellstore.hpp" namespace MWClass { 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); } } void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const { } std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); } bool BodyPart::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } void BodyPart::registerSelf() { std::shared_ptr instance (new BodyPart); registerClass (typeid (ESM::BodyPart).name(), instance); } std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } } openmw-openmw-0.47.0/apps/openmw/mwclass/bodypart.hpp000066400000000000000000000020501413061077700226730ustar00rootroot00000000000000#ifndef GAME_MWCLASS_BODYPART_H #define GAME_MWCLASS_BODYPART_H #include "../mwworld/class.hpp" namespace MWClass { class BodyPart : public MWWorld::Class { 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, 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) static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/book.cpp000066400000000000000000000137011413061077700220010ustar00rootroot00000000000000#include "book.hpp" #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" namespace MWClass { void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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(); const ESM::Sound *sound = store.get().searchRandom("WolfItem"); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } return std::shared_ptr(new MWWorld::ActionRead(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; } void Book::registerSelf() { std::shared_ptr instance (new Book); registerClass (typeid (ESM::Book).name(), instance); } 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=""; 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::shared_ptr Book::use (const MWWorld::Ptr& ptr, bool force) const { return std::shared_ptr(new MWWorld::ActionRead(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.47.0/apps/openmw/mwclass/book.hpp000066400000000000000000000055671413061077700220210ustar00rootroot00000000000000#ifndef GAME_MWCLASS_BOOK_H #define GAME_MWCLASS_BOOK_H #include "../mwworld/class.hpp" namespace MWClass { class Book : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/classes.cpp000066400000000000000000000023731413061077700225070ustar00rootroot00000000000000#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.47.0/apps/openmw/mwclass/classes.hpp000066400000000000000000000002351413061077700225070ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CLASSES_H #define GAME_MWCLASS_CLASSES_H namespace MWClass { void registerClasses(); ///< register all known classes } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/clothing.cpp000066400000000000000000000224741413061077700226650ustar00rootroot00000000000000#include "clothing.hpp" #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" namespace MWClass { void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Clothing::registerSelf() { std::shared_ptr instance (new Clothing); registerClass (typeid (ESM::Clothing).name(), instance); } 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=""; 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::shared_ptr Clothing::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/clothing.hpp000066400000000000000000000072631413061077700226710ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CLOTHING_H #define GAME_MWCLASS_CLOTHING_H #include "../mwworld/class.hpp" namespace MWClass { class Clothing : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/container.cpp000066400000000000000000000263051413061077700230350ustar00rootroot00000000000000#include "container.hpp" #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/nullaction.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.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/actorutil.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWClass { ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max()); // 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() { 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, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } bool Container::useAnim() const { return true; } std::shared_ptr Container::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::shared_ptr (new MWWorld::NullAction ()); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfContainer"); std::shared_ptr action(new MWWorld::FailedAction("#{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)) { std::shared_ptr action (new MWWorld::ActionHarvest(ptr)); return action; } std::shared_ptr action (new MWWorld::ActionOpen(ptr)); return action; } else { // Activate trap std::shared_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); action->setSound(trapActivationSound); return action; } } else { std::shared_ptr action(new MWWorld::FailedAction(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; } void Container::registerSelf() { std::shared_ptr instance (new Container); registerClass (typeid (ESM::Container).name(), instance); } 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.47.0/apps/openmw/mwclass/container.hpp000066400000000000000000000073051413061077700230410ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H #include "../mwworld/class.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::Class { bool mHarvestEnabled; 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: Container(); 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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.47.0/apps/openmw/mwclass/creature.cpp000066400000000000000000001062731413061077700226700ustar00rootroot00000000000000#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 "../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 "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" #include "../mwgui/tooltips.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/actorutil.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) { } const Creature::GMST& Creature::getGmst() { static GMST gmst; static bool inited = false; if (!inited) { 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"); inited = true; } return gmst; } void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { std::unique_ptr data (new CreatureCustomData); 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::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_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); data->mCreatureStats.setNeedRecalcDynamicStats(false); // store ptr.getRefData().setCustomData(std::move(data)); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); 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 { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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->getTypeName() == typeid(ESM::Weapon).name()) 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); if(Misc::Rng::roll0to99() >= 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::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } // 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(); if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) 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::shared_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(); const ESM::Sound *sound = store.get().searchRandom("WolfCreature"); std::shared_ptr action(new MWWorld::FailedAction("#{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::shared_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); } else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) return std::shared_ptr(new MWWorld::ActionTalk(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::shared_ptr(new MWWorld::ActionOpen(ptr)); return std::shared_ptr(new MWWorld::FailedAction("")); } 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); } void Creature::registerSelf() { std::shared_ptr instance (new Creature); registerClass (typeid (ESM::Creature).name(), instance); } 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->mPersistent; } 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()) { for (const ESM::Creature &creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() && Misc::StringUtils::ciEqual(model, "meshes\\" + creature.mModel)) { 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; } } } } if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->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; if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { // Create a CustomData, but don't fill it from ESM records (not needed) std::unique_ptr data (new CreatureCustomData); 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(); const ESM::CreatureState& creatureState = state.asCreatureState(); 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; } if (ptr.getRefData().getCount() <= 0) { state.mHasCustomState = false; return; } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); 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); ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); } } } 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::CreatureStats::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 GMST& gmst = getGmst(); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); return getWalkSpeed(ptr) * (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()); } } openmw-openmw-0.47.0/apps/openmw/mwclass/creature.hpp000066400000000000000000000145351413061077700226740ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H #include "actor.hpp" namespace ESM { struct GameSetting; } namespace MWClass { class Creature : public Actor { 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::shared_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; static void registerSelf(); 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::CreatureStats::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.47.0/apps/openmw/mwclass/creaturelevlist.cpp000066400000000000000000000150161413061077700242650ustar00rootroot00000000000000#include "creaturelevlist.hpp" #include #include #include "../mwmechanics/levelledlist.hpp" #include "../mwworld/customdata.hpp" #include "../mwmechanics/creaturestats.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; } }; 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 = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(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::registerSelf() { std::shared_ptr instance (new CreatureLevList); registerClass (typeid (ESM::CreatureLevList).name(), instance); } 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(); std::string id = MWMechanics::getLevelledItem(ref->mBase, true); 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.47.0/apps/openmw/mwclass/creaturelevlist.hpp000066400000000000000000000030051413061077700242650ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CREATURELEVLIST_H #define GAME_MWCLASS_CREATURELEVLIST_H #include "../mwworld/class.hpp" namespace MWClass { class CreatureLevList : public MWWorld::Class { 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) static void registerSelf(); 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/door.cpp000066400000000000000000000322501413061077700220120ustar00rootroot00000000000000#include "door.hpp" #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 "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.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; } }; 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, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // 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); } } } bool Door::isDoor() const { return true; } bool Door::useAnim() const { return true; } std::string Door::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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::shared_ptr action(new MWWorld::FailedAction(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::shared_ptr action(new MWWorld::ActionTrap(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 std::shared_ptr action(new MWWorld::FailedAction); return action; } else { std::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true)); action->setSound(openSound); return action; } } else { // animated door std::shared_ptr action(new MWWorld::ActionDoor(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::shared_ptr action(new MWWorld::FailedAction(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; } void Door::registerSelf() { std::shared_ptr instance (new Door); registerClass (typeid (ESM::Door).name(), instance); } 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 int x,y; auto world = MWBase::Environment::get().getWorld(); world->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); const ESM::Cell* cell = world->getStore().get().search(x,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.47.0/apps/openmw/mwclass/door.hpp000066400000000000000000000052051413061077700220170ustar00rootroot00000000000000#ifndef GAME_MWCLASS_DOOR_H #define GAME_MWCLASS_DOOR_H #include #include "../mwworld/class.hpp" namespace MWClass { class Door : public MWWorld::Class { 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, 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::shared_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 static void registerSelf(); 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.47.0/apps/openmw/mwclass/ingredient.cpp000066400000000000000000000133101413061077700231730ustar00rootroot00000000000000#include "ingredient.hpp" #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" namespace MWClass { void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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::shared_ptr Ingredient::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action (new MWWorld::ActionEat (ptr)); action->setSound ("Swallow"); return action; } void Ingredient::registerSelf() { std::shared_ptr instance (new Ingredient); registerClass (typeid (ESM::Ingredient).name(), instance); } 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.47.0/apps/openmw/mwclass/ingredient.hpp000066400000000000000000000045531413061077700232110ustar00rootroot00000000000000#ifndef GAME_MWCLASS_INGREDIENT_H #define GAME_MWCLASS_INGREDIENT_H #include "../mwworld/class.hpp" namespace MWClass { class Ingredient : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu static void registerSelf(); 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.47.0/apps/openmw/mwclass/itemlevlist.cpp000066400000000000000000000007411413061077700234100ustar00rootroot00000000000000#include "itemlevlist.hpp" #include namespace MWClass { std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } bool ItemLevList::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } void ItemLevList::registerSelf() { std::shared_ptr instance (new ItemLevList); registerClass (typeid (ESM::ItemLevList).name(), instance); } } openmw-openmw-0.47.0/apps/openmw/mwclass/itemlevlist.hpp000066400000000000000000000010761413061077700234170ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ITEMLEVLIST_H #define GAME_MWCLASS_ITEMLEVLIST_H #include "../mwworld/class.hpp" namespace MWClass { class ItemLevList : public MWWorld::Class { 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) static void registerSelf(); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/light.cpp000066400000000000000000000175561413061077700221720ustar00rootroot00000000000000#include "light.hpp" #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" namespace MWClass { 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, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != nullptr); // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) physics.addObject(ptr, model); 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); } bool Light::useAnim() const { return true; } std::string Light::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_ptr Light::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::shared_ptr(new MWWorld::NullAction()); MWWorld::LiveCellRef *ref = ptr.get(); if(!(ref->mBase->mData.mFlags&ESM::Light::Carry)) return std::shared_ptr(new MWWorld::FailedAction()); 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; } void Light::registerSelf() { std::shared_ptr instance (new Light); registerClass (typeid (ESM::Light).name(), instance); } 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::shared_ptr Light::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/light.hpp000066400000000000000000000066121413061077700221660ustar00rootroot00000000000000#ifndef GAME_MWCLASS_LIGHT_H #define GAME_MWCLASS_LIGHT_H #include "../mwworld/class.hpp" namespace MWClass { class Light : public MWWorld::Class { 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, 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::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/lockpick.cpp000066400000000000000000000132371413061077700226520ustar00rootroot00000000000000#include "lockpick.hpp" #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" namespace MWClass { void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Lockpick::registerSelf() { std::shared_ptr instance (new Lockpick); registerClass (typeid (ESM::Lockpick).name(), instance); } 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::shared_ptr Lockpick::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/lockpick.hpp000066400000000000000000000061041413061077700226520ustar00rootroot00000000000000#ifndef GAME_MWCLASS_LOCKPICK_H #define GAME_MWCLASS_LOCKPICK_H #include "../mwworld/class.hpp" namespace MWClass { class Lockpick : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/misc.cpp000066400000000000000000000211051413061077700217770ustar00rootroot00000000000000#include "misc.hpp" #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" namespace MWClass { 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); } } void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Miscellaneous::registerSelf() { std::shared_ptr instance (new Miscellaneous); registerClass (typeid (ESM::Miscellaneous).name(), instance); } 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::shared_ptr Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const { if (ptr.getCellRef().getSoul().empty() || !MWBase::Environment::get().getWorld()->getStore().get().search(ptr.getCellRef().getSoul())) return std::shared_ptr(new MWWorld::NullAction()); else return std::shared_ptr(new MWWorld::ActionSoulgem(ptr)); } 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.47.0/apps/openmw/mwclass/misc.hpp000066400000000000000000000047541413061077700220170ustar00rootroot00000000000000#ifndef GAME_MWCLASS_MISC_H #define GAME_MWCLASS_MISC_H #include "../mwworld/class.hpp" namespace MWClass { class Miscellaneous : public MWWorld::Class { 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 void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/npc.cpp000066400000000000000000001730501413061077700216330ustar00rootroot00000000000000#include "npc.hpp" #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 "../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 "../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 "../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 { 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 GMST gmst; static bool inited = false; if(!inited) { 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"); inited = true; } return gmst; } void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { std::unique_ptr data(new NpcCustomData); 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); data->mNpcStats.setNeedRecalcDynamicStats(false); } 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); data->mNpcStats.setNeedRecalcDynamicStats(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::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells if (!spellsInitialised) data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); data->mNpcStats.setGoldPool(gold); // store ptr.getRefData().setCustomData(std::move(data)); 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->mPersistent; } 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")); if (!npc->mBase->mModel.empty()) models.push_back("meshes/"+npc->mBase->mModel); if (!npc->mBase->mHead.empty()) { const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHead); if (head) models.push_back("meshes/"+head->mModel); } if (!npc->mBase->mHair.empty()) { const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHair); if (hair) models.push_back("meshes/"+hair->mModel); } 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->getTypeName() == typeid(ESM::Clothing).name()) { const ESM::Clothing *clothes = equipped->get()->mBase; parts = clothes->mParts.mParts; } else if(equipped->getTypeName() == typeid(ESM::Armor).name()) { 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("meshes/"+part->mModel); } } } // 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("meshes/"+part->mModel); } } } 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.getTypeName() != typeid(ESM::Weapon).name()) 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() >= 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::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); 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(); if (Misc::Rng::roll0to99() < 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()) 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)]; 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.getTypeName() == typeid(ESM::Armor).name(); // 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) == 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.getTypeName() == typeid(ESM::Armor).name(); } } if (hasArmor) { if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition { 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::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { // player got activated by another NPC if(ptr == MWMechanics::getPlayer()) return std::shared_ptr(new MWWorld::ActionTalk(actor)); // Werewolfs can't activate NPCs if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfNPC"); std::shared_ptr action(new MWWorld::FailedAction("#{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::shared_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); } else if (!stats.getAiSequence().isInCombat()) { if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing // Can't talk to werewolves if (!getNpcStats(ptr).isWerewolf()) return std::shared_ptr(new MWWorld::ActionTalk(ptr)); } else // In combat { const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); if (stealingInCombat && stats.getKnockedDown()) return std::shared_ptr(new MWWorld::ActionOpen(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::shared_ptr(new MWWorld::ActionOpen(ptr)); return std::shared_ptr (new MWWorld::FailedAction("")); } 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::CreatureStats& stats = getCreatureStats(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 NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.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*(npcdata->mNpcStats.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(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.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::CreatureStats& stats = getCreatureStats(ptr); bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) return 0.f; const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.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 *= npcdata->mNpcStats.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; } void Npc::registerSelf() { std::shared_ptr instance (new Npc); registerClass (typeid (ESM::NPC).name(), instance); } 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::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { MWMechanics::CastSpell cast(ptr, ptr); return cast.cast(id); } 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->getTypeName() != typeid(ESM::Armor).name()) { // 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->getTypeName() != typeid(ESM::Armor).name()) 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; if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { // 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(); const ESM::NpcState& npcState = state.asNpcState(); 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; } if (ptr.getRefData().getCount() <= 0) { state.mHasCustomState = false; return; } const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); 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); ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); } } } 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::CreatureStats::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 NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f * npcdata->mNpcStats.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 GMST& gmst = getGmst(); const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects& mageffects = npcdata->mNpcStats.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)); float swimSpeed; if (running) swimSpeed = getRunSpeed(ptr); else swimSpeed = getWalkSpeed(ptr); swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat(); return swimSpeed; } } openmw-openmw-0.47.0/apps/openmw/mwclass/npc.hpp000066400000000000000000000177161413061077700216460ustar00rootroot00000000000000#ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H #include "actor.hpp" namespace ESM { struct GameSetting; } namespace MWClass { class Npc : public Actor { 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::shared_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 apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const override; ///< Apply \a id on \a ptr. /// \param actor Actor that is resposible for the ID being applied to \a ptr. /// \return Any effect? 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; static void registerSelf(); 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::CreatureStats::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.47.0/apps/openmw/mwclass/potion.cpp000066400000000000000000000117571413061077700223700ustar00rootroot00000000000000#include "potion.hpp" #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" namespace MWClass { void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Potion::registerSelf() { std::shared_ptr instance (new Potion); registerClass (typeid (ESM::Potion).name(), instance); } 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::shared_ptr Potion::use (const MWWorld::Ptr& ptr, bool force) const { MWWorld::LiveCellRef *ref = ptr.get(); std::shared_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.47.0/apps/openmw/mwclass/potion.hpp000066400000000000000000000045231413061077700223660ustar00rootroot00000000000000#ifndef GAME_MWCLASS_POTION_H #define GAME_MWCLASS_POTION_H #include "../mwworld/class.hpp" namespace MWClass { class Potion : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu static void registerSelf(); 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.47.0/apps/openmw/mwclass/probe.cpp000066400000000000000000000130551413061077700221600ustar00rootroot00000000000000#include "probe.hpp" #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" namespace MWClass { void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Probe::registerSelf() { std::shared_ptr instance (new Probe); registerClass (typeid (ESM::Probe).name(), instance); } 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::shared_ptr Probe::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/probe.hpp000066400000000000000000000060731413061077700221670ustar00rootroot00000000000000#ifndef GAME_MWCLASS_PROBE_H #define GAME_MWCLASS_PROBE_H #include "../mwworld/class.hpp" namespace MWClass { class Probe : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/repair.cpp000066400000000000000000000114521413061077700223320ustar00rootroot00000000000000#include "repair.hpp" #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" namespace MWClass { void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Repair::registerSelf() { std::shared_ptr instance (new Repair); registerClass (typeid (ESM::Repair).name(), instance); } 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::shared_ptr Repair::use (const MWWorld::Ptr& ptr, bool force) const { return std::shared_ptr(new MWWorld::ActionRepair(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.47.0/apps/openmw/mwclass/repair.hpp000066400000000000000000000054471413061077700223460ustar00rootroot00000000000000#ifndef GAME_MWCLASS_REPAIR_H #define GAME_MWCLASS_REPAIR_H #include "../mwworld/class.hpp" namespace MWClass { class Repair : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwclass/static.cpp000066400000000000000000000035061413061077700223400ustar00rootroot00000000000000#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" namespace MWClass { 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, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } std::string Static::getName (const MWWorld::ConstPtr& ptr) const { return ""; } bool Static::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } void Static::registerSelf() { std::shared_ptr instance (new Static); registerClass (typeid (ESM::Static).name(), instance); } 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.47.0/apps/openmw/mwclass/static.hpp000066400000000000000000000021141413061077700223370ustar00rootroot00000000000000#ifndef GAME_MWCLASS_STATIC_H #define GAME_MWCLASS_STATIC_H #include "../mwworld/class.hpp" namespace MWClass { class Static : public MWWorld::Class { 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, 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) static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwclass/weapon.cpp000066400000000000000000000310731413061077700223420ustar00rootroot00000000000000#include "weapon.hpp" #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" namespace MWClass { void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects } std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { return "meshes\\" + model; } return ""; } 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::shared_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; } void Weapon::registerSelf() { std::shared_ptr instance (new Weapon); registerClass (typeid (ESM::Weapon).name(), instance); } 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=""; 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::shared_ptr Weapon::use (const MWWorld::Ptr& ptr, bool force) const { std::shared_ptr action(new MWWorld::ActionEquip(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.47.0/apps/openmw/mwclass/weapon.hpp000066400000000000000000000077601413061077700223550ustar00rootroot00000000000000#ifndef GAME_MWCLASS_WEAPON_H #define GAME_MWCLASS_WEAPON_H #include "../mwworld/class.hpp" namespace MWClass { class Weapon : public MWWorld::Class { 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, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::shared_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. static void registerSelf(); 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::shared_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.47.0/apps/openmw/mwdialogue/000077500000000000000000000000001413061077700210255ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwdialogue/dialoguemanagerimp.cpp000066400000000000000000000617151413061077700253750ustar00rootroot00000000000000#include "dialoguemanagerimp.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 "../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) , mTemporaryDispositionChange(0.f) , mPermanentDispositionChange(0.f) { mChoice = -1; mIsInChoice = false; mGoodbye = false; mCompilerContext.setExtensions (&extensions); } void DialogueManager::clear() { mKnownTopics.clear(); mTalkedTo = false; mTemporaryDispositionChange = 0; mPermanentDispositionChange = 0; } void DialogueManager::addTopic (const std::string& topic) { mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); } void DialogueManager::parseText (const std::string& text) { updateActorKnownTopics(); 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); } if (mActorKnownTopics.count( topicId )) mKnownTopics.insert( topicId ); } } 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 = ""; mPermanentDispositionChange = 0; mTemporaryDispositionChange = 0; mChoice = -1; mIsInChoice = false; mGoodbye = false; mChoices.clear(); mActor = actor; MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats (actor); mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); mActorKnownTopicsFlag.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; parseText (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 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); parseText (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(); mActorKnownTopicsFlag.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 flag = 0; if(!inJournal(topicId, answer->mId)) { // Does this dialogue contains some actor-specific answer? if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) flag |= MWBase::DialogueManager::TopicType::Specific; } else flag |= MWBase::DialogueManager::TopicType::Exhausted; mActorKnownTopics.insert (dialog.mId); mActorKnownTopicsFlag[dialog.mId] = flag; } } } } std::list DialogueManager::getAvailableTopics() { updateActorKnownTopics(); std::list keywordList; for (const std::string& topic : 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) { return mActorKnownTopicsFlag[topicId]; } 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 (mActor.getClass().isNpc()) { // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); if (curDisp + mPermanentDispositionChange < 0) mPermanentDispositionChange = -curDisp; MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor); npcStats.setBaseDisposition(static_cast(npcStats.getBaseDisposition() + mPermanentDispositionChange)); } mPermanentDispositionChange = 0; mTemporaryDispositionChange = 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; parseText (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 (const std::string& text, int choice) { mIsInChoice = true; mChoices.emplace_back(text, choice); } const std::vector >& DialogueManager::getChoices() { return mChoices; } bool DialogueManager::isGoodbye() { return mGoodbye; } void DialogueManager::goodbye() { mIsInChoice = false; mGoodbye = true; } void DialogueManager::persuade(int type, ResponseCallback* callback) { bool success; float temp, perm; MWBase::Environment::get().getMechanicsManager()->getPersuasionDispositionChange( mActor, MWBase::MechanicsManager::PersuasionType(type), success, temp, perm); mTemporaryDispositionChange += temp; mPermanentDispositionChange += perm; // change temp disposition so that final disposition is between 0...100 float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); if (curDisp + mTemporaryDispositionChange < 0) mTemporaryDispositionChange = -curDisp; else if (curDisp + mTemporaryDispositionChange > 100) mTemporaryDispositionChange = 100 - curDisp; 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); } int DialogueManager::getTemporaryDispositionChange() const { return static_cast(mTemporaryDispositionChange); } void DialogueManager::applyBarterDispositionChange(int delta) { mTemporaryDispositionChange += delta; 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]; parseText (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; for (std::set::const_iterator iter (mKnownTopics.begin()); iter!=mKnownTopics.end(); ++iter) { state.mKnownTopics.push_back (*iter); } 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(const std::string &faction1, const std::string &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(const std::string &faction1, const std::string &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(const std::string &faction1, const std::string &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.47.0/apps/openmw/mwdialogue/dialoguemanagerimp.hpp000066400000000000000000000107411413061077700253730ustar00rootroot00000000000000#ifndef GAME_MWDIALOG_DIALOGUEMANAGERIMP_H #define GAME_MWDIALOG_DIALOGUEMANAGERIMP_H #include "../mwbase/dialoguemanager.hpp" #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 { std::set mKnownTopics;// Those are the topics the player knows. // Modified faction reactions. > typedef std::map > ModFactionReactionMap; ModFactionReactionMap mChangedFactionReaction; std::set mActorKnownTopics; std::unordered_map mActorKnownTopicsFlag; 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; float mTemporaryDispositionChange; float mPermanentDispositionChange; void parseText (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); 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) override; bool inJournal (const std::string& topicId, const std::string& infoId) override; void addTopic (const std::string& topic) override; void addChoice (const std::string& text,int choice) override; const std::vector >& getChoices() override; bool isGoodbye() 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; int getTemporaryDispositionChange () const 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 (const std::string& faction1, const std::string& faction2, int diff) override; void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) override; /// @return faction1's opinion of faction2 int getFactionReaction (const std::string& faction1, const std::string& 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.47.0/apps/openmw/mwdialogue/filter.cpp000066400000000000000000000551371413061077700230310ustar00rootroot00000000000000#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.getTypeName() != typeid (ESM::NPC).name()); // 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.getTypeName() != typeid (ESM::NPC).name()); 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.getTypeName() != typeid (ESM::NPC).name())) // 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(); float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / player.getClass().getCreatureStats (player).getHealth().getModified(); return select.selectCompare (static_cast(ratio*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: { float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); return select.selectCompare (static_cast(ratio*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::CreatureStats::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.getTypeName() == typeid(ESM::Creature).name()) 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.47.0/apps/openmw/mwdialogue/filter.hpp000066400000000000000000000053061413061077700230270ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/hypertextparser.cpp000066400000000000000000000063611413061077700250100ustar00rootroot00000000000000#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 MWWorld::Store & dialogs = MWBase::Environment::get().getWorld()->getStore().get(); std::list keywordList; for (MWWorld::Store::iterator it = dialogs.begin(); it != dialogs.end(); ++it) keywordList.push_back(Misc::StringUtils::lowerCase(it->mId)); keywordList.sort(Misc::StringUtils::ciLess); KeywordSearch keywordSearch; for (std::list::const_iterator it = keywordList.begin(); it != keywordList.end(); ++it) keywordSearch.seed(*it, 0 /*unused*/); 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.47.0/apps/openmw/mwdialogue/hypertextparser.hpp000066400000000000000000000016331413061077700250120ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/journalentry.cpp000066400000000000000000000103511413061077700242650ustar00rootroot00000000000000#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() {} 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() {} 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.47.0/apps/openmw/mwdialogue/journalentry.hpp000066400000000000000000000034631413061077700243000ustar00rootroot00000000000000#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(); /// 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(); 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.47.0/apps/openmw/mwdialogue/journalimp.cpp000066400000000000000000000202741413061077700237160ustar00rootroot00000000000000#include "journalimp.hpp" #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); quest.addEntry (entry); // we are doing slicing on purpose here // 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.47.0/apps/openmw/mwdialogue/journalimp.hpp000066400000000000000000000052111413061077700237150ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/keywordsearch.cpp000066400000000000000000000000001413061077700243710ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwdialogue/keywordsearch.hpp000066400000000000000000000201761413061077700244160ustar00rootroot00000000000000#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 (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) { std::vector matches; for (Point i = beg; i != end; ++i) { // check if previous character marked start of new word if (i != beg) { Point prev = i; --prev; if(isalpha(*prev)) continue; } // check first character typename Entry::childen_t::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::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 = /*std::move*/ (j->second.mValue); string_t pushKeyword = /*std::move*/ (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.47.0/apps/openmw/mwdialogue/quest.cpp000066400000000000000000000051621413061077700226760ustar00rootroot00000000000000#include "quest.hpp" #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::addEntry (const JournalEntry& entry) { int index = -1; const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == entry.mInfoId) { index = iter->mData.mJournalIndex; break; } if (index==-1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); for (auto &info : dialogue->mInfo) { if (info.mData.mJournalIndex == index && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) { mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); break; } } if (index > mIndex) mIndex = index; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) return; mEntries.push_back (entry); // we want slicing here } void Quest::write (ESM::QuestState& state) const { state.mTopic = getTopic(); state.mState = mIndex; state.mFinished = mFinished; } } openmw-openmw-0.47.0/apps/openmw/mwdialogue/quest.hpp000066400000000000000000000017751413061077700227110ustar00rootroot00000000000000#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 addEntry (const JournalEntry& entry) override; ///< Add entry and adjust index accordingly. /// /// \note Redundant entries are ignored, but the index is still adjusted. void write (ESM::QuestState& state) const; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwdialogue/scripttest.cpp000066400000000000000000000102561413061077700237410ustar00rootroot00000000000000#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" 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.47.0/apps/openmw/mwdialogue/scripttest.hpp000066400000000000000000000007021413061077700237410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/selectwrapper.cpp000066400000000000000000000220671413061077700244200ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/selectwrapper.hpp000066400000000000000000000051331413061077700244200ustar00rootroot00000000000000#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.47.0/apps/openmw/mwdialogue/topic.cpp000066400000000000000000000032371413061077700226540ustar00rootroot00000000000000#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() {} void 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; } mEntries.push_back (entry); // we want slicing here } 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.47.0/apps/openmw/mwdialogue/topic.hpp000066400000000000000000000026121413061077700226550ustar00rootroot00000000000000#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 void 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.47.0/apps/openmw/mwgui/000077500000000000000000000000001413061077700200205ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwgui/alchemywindow.cpp000066400000000000000000000377731413061077700234170ustar00rootroot00000000000000#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.getTypeName() != typeid(ESM::Ingredient).name()) 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.47.0/apps/openmw/mwgui/alchemywindow.hpp000066400000000000000000000053411413061077700234060ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/backgroundimage.cpp000066400000000000000000000026111413061077700236460ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/backgroundimage.hpp000066400000000000000000000016621413061077700236600ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/birth.cpp000066400000000000000000000214461413061077700216430ustar00rootroot00000000000000#include "birth.hpp" #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 = 18; MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), 18); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::BirthSign *birth = store.get().find(mCurrentBirthId); mBirthImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctTexturePath(birth->mTexture)); 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.47.0/apps/openmw/mwgui/birth.hpp000066400000000000000000000026131413061077700216430ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/bookpage.cpp000066400000000000000000001234771413061077700223310ustar00rootroot00000000000000#include "bookpage.hpp" #include #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" #include "MyGUI_FactoryManager.h" #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" 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 (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 () { } 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); #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) // work around inconsistency in MyGUI where the mouse press coordinates aren't // transformed by the current Layer (even though mouse *move* events are). if(!move) pos = mNode->getLayer()->getPosition(left, top); #endif 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->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 ()) { std::unique_ptr textFormat(new TextFormat (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()); GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), -1 /*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.47.0/apps/openmw/mwgui/bookpage.hpp000066400000000000000000000145661413061077700223340ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/bookwindow.cpp000066400000000000000000000154201413061077700227100ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/bookwindow.hpp000066400000000000000000000033031413061077700227120ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/charactercreation.cpp000066400000000000000000000731451413061077700242170ustar00rootroot00000000000000#include "charactercreation.hpp" #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}; } } void updatePlayerHealth() { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); npcStats.updateHealth(); } } 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 = ""; 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; } updatePlayerHealth(); } 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; } updatePlayerHealth(); } 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; } updatePlayerHealth(); } 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; 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); } updatePlayerHealth(); } 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; updatePlayerHealth(); } 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.47.0/apps/openmw/mwgui/charactercreation.hpp000066400000000000000000000074741413061077700242260ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/class.cpp000066400000000000000000000774331413061077700216470ustar00rootroot00000000000000#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 "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; for(int i = 0; i < 5; i++) { v.push_back(mMajorSkill[i]->getSkillId()); } return v; } std::vector CreateClassDialog::getMinorSkills() const { std::vector v; 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) { std::string classImage = std::string("textures\\levelup\\") + classId + ".dds"; if (!MWBase::Environment::get().getWindowManager()->textureExists(classImage)) { Log(Debug::Warning) << "No class image for " << classId << ", falling back to default"; classImage = "textures\\levelup\\warrior.dds"; } imageBox->setImageTexture(classImage); } } openmw-openmw-0.47.0/apps/openmw/mwgui/class.hpp000066400000000000000000000224651413061077700216470ustar00rootroot00000000000000#ifndef MWGUI_CLASS_H #define MWGUI_CLASS_H #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.47.0/apps/openmw/mwgui/companionitemmodel.cpp000066400000000000000000000026711413061077700244150ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/companionitemmodel.hpp000066400000000000000000000012341413061077700244140ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/companionwindow.cpp000066400000000000000000000131101413061077700237330ustar00rootroot00000000000000#include "companionwindow.hpp" #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.47.0/apps/openmw/mwgui/companionwindow.hpp000066400000000000000000000030201413061077700237370ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/confirmationdialog.cpp000066400000000000000000000030221413061077700243710ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/confirmationdialog.hpp000066400000000000000000000015541413061077700244060ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/console.cpp000066400000000000000000000405771413061077700222030ustar00rootroot00000000000000#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 "../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, const std::string& color) { mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { print(msg + "\n", "#FF00FF"); } void Console::printError(const std::string &msg) { print(msg + "\n", "#FF2222"); } void Console::execute (const std::string& command) { // Log the command print("> " + command + "\n"); 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) { 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(); // 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) { setTitle("#{sConsoleTitle}"); mPtr=MWWorld::Ptr(); } else { setTitle("#{sConsoleTitle} (" + object.getCellRef().getRefId() + ")"); mPtr = object; } // User clicked on an object. Restore focus to the console command line. MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else { setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); } } void Console::onReferenceUnavailable() { setSelectedObject(MWWorld::Ptr()); } void Console::resetReference() { ReferenceInterface::resetReference(); setSelectedObject(MWWorld::Ptr()); } } openmw-openmw-0.47.0/apps/openmw/mwgui/console.hpp000066400000000000000000000060071413061077700221760ustar00rootroot00000000000000#ifndef MWGUI_CONSOLE_H #define MWGUI_CONSOLE_H #include #include #include #include #include #include #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, const std::string& color = "#FFFFFF"); // 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; protected: void onReferenceUnavailable() override; private: 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.47.0/apps/openmw/mwgui/container.cpp000066400000000000000000000253461413061077700225200ustar00rootroot00000000000000#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) { 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) { mPtr = container; bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); if (mPtr.getClass().hasInventoryStore(mPtr)) { if (mPtr.getClass().isNpc() && !loot) { // 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 std::map& 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()) { MWMechanics::purgeSummonEffect(summoner, *it); summons.erase(it); 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); } } openmw-openmw-0.47.0/apps/openmw/mwgui/container.hpp000066400000000000000000000030121413061077700225070ustar00rootroot00000000000000#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; private: DragAndDrop* mDragAndDrop; MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; int mSelectedItem; 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.47.0/apps/openmw/mwgui/containeritemmodel.cpp000066400000000000000000000172001413061077700244060ustar00rootroot00000000000000#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 (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.getTypeName() != typeid(ESM::Container).name()) 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; } } openmw-openmw-0.47.0/apps/openmw/mwgui/containeritemmodel.hpp000066400000000000000000000030571413061077700244200ustar00rootroot00000000000000#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 (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; private: std::vector> mItemSources; std::vector mWorldItems; const bool mTrading; std::vector mItems; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/controllers.cpp000066400000000000000000000007061413061077700230750ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/controllers.hpp000066400000000000000000000010721413061077700230770ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/countdialog.cpp000066400000000000000000000060451413061077700230410ustar00rootroot00000000000000#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 + 128, 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.47.0/apps/openmw/mwgui/countdialog.hpp000066400000000000000000000023131413061077700230400ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/cursor.cpp000066400000000000000000000040031413061077700220360ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/cursor.hpp000066400000000000000000000025561413061077700220560ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/debugwindow.cpp000066400000000000000000000100001413061077700230310ustar00rootroot00000000000000#include "debugwindow.hpp" #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 // - Log viewer // - Material editor // - Shader editor MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler"); mBulletProfilerEdit = item->createWidgetReal ("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); mMainWidget->setSize(viewSize); } void DebugWindow::onFrame(float dt) { #ifndef BT_NO_PROFILE if (!isVisible()) return; static float timer = 0; timer -= dt; if (timer > 0) return; timer = 1; 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 } } openmw-openmw-0.47.0/apps/openmw/mwgui/debugwindow.hpp000066400000000000000000000005501413061077700230470ustar00rootroot00000000000000#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; private: MyGUI::TabControl* mTabControl; MyGUI::EditBox* mBulletProfilerEdit; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/dialogue.cpp000066400000000000000000000760411413061077700223250ustar00rootroot00000000000000#include "dialogue.hpp" #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 "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) { getWidget(mCancelButton, "CancelButton"); getWidget(mAdmireButton, "AdmireButton"); getWidget(mIntimidateButton, "IntimidateButton"); getWidget(mTauntButton, "TauntButton"); getWidget(mBribe10Button, "Bribe10Button"); getWidget(mBribe100Button, "Bribe100Button"); getWidget(mBribe1000Button, "Bribe1000Button"); getWidget(mGoldLabel, "GoldLabel"); 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::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)); 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::min(static_cast(mScrollBar->getScrollRange()-1), std::max(0, static_cast(mScrollBar->getScrollPosition() - _rel*0.3)))); 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(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.getTypeName() == typeid(ESM::NPC).name() && !mPtr.get()->mBase->getTransport().empty()) || (mPtr.getTypeName() == typeid(ESM::Creature).name() && !mPtr.get()->mBase->getTransport().empty()); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) 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); 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.47.0/apps/openmw/mwgui/dialogue.hpp000066400000000000000000000123171413061077700223260ustar00rootroot00000000000000#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 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; MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; MyGUI::Button* mIntimidateButton; MyGUI::Button* mTauntButton; MyGUI::Button* mBribe10Button; MyGUI::Button* mBribe100Button; MyGUI::Button* mBribe1000Button; MyGUI::TextBox* mGoldLabel; 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(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.47.0/apps/openmw/mwgui/draganddrop.cpp000066400000000000000000000110061413061077700230070ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/draganddrop.hpp000066400000000000000000000013721413061077700230210ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/enchantingdialog.cpp000066400000000000000000000327001413061077700240240ustar00rootroot00000000000000#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::max(0, std::min(100, mEnchanting.getEnchantChance())))); 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.47.0/apps/openmw/mwgui/enchantingdialog.hpp000066400000000000000000000041161413061077700240310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/exposedwindow.cpp000066400000000000000000000011661413061077700234270ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/exposedwindow.hpp000066400000000000000000000010301413061077700234220ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/formatting.cpp000066400000000000000000000435551413061077700227120ustar00rootroot00000000000000#include "formatting.hpp" #include #include #include #include // correctBookartPath #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include #include #include #include "../mwscript/interpretercontext.hpp" namespace MWGui { namespace 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")); bool exists; std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists); 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"); mTextStyle.mFont = "Journalbook "+face; } 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.47.0/apps/openmw/mwgui/formatting.hpp000066400000000000000000000127201413061077700227050ustar00rootroot00000000000000#ifndef MWGUI_FORMATTING_H #define MWGUI_FORMATTING_H #include #include #include namespace MWGui { namespace Formatting { struct TextStyle { TextStyle() : mColour(0,0,0) , mFont("Journalbook Magic Cards") , 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.47.0/apps/openmw/mwgui/hud.cpp000066400000000000000000000540341413061077700213120ustar00rootroot00000000000000#include "hud.hpp" #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/creaturestats.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 (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"); } 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) , mDrowningFrame(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) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); // 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(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) { mDrowningFrame->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)); } if (mIsDrowning) mDrowningFlashTheta += dt * osg::PI*2; mSpellIcons->updateWidgets(mEffectBox, true); if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) { updateEnemyHealthBar(); } if (mIsDrowning) { 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 = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); 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().getCurrent() / stats.getHealth().getModified() * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); } 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.47.0/apps/openmw/mwgui/hud.hpp000066400000000000000000000075371413061077700213250ustar00rootroot00000000000000#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 *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.47.0/apps/openmw/mwgui/inventoryitemmodel.cpp000066400000000000000000000077011413061077700244660ustar00rootroot00000000000000#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 (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; } } openmw-openmw-0.47.0/apps/openmw/mwgui/inventoryitemmodel.hpp000066400000000000000000000016671413061077700245000ustar00rootroot00000000000000#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 (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; protected: MWWorld::Ptr mActor; private: std::vector mItems; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/inventorywindow.cpp000066400000000000000000000745431413061077700240260ustar00rootroot00000000000000#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/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" #include "draganddrop.hpp" #include "widgets.hpp" #include "tooltips.hpp" namespace { bool isRightHandWeapon(const MWWorld::Ptr& item) { if (item.getClass().getTypeName() != typeid(ESM::Weapon).name()) 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(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) , mUpdateTimer(0.f) { mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); 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.getTypeName() == typeid(ESM::Weapon).name(); 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() { MyGUI::IntSize size = mAvatarImage->getSize(); int width = std::min(mPreview->getTextureWidth(), size.width); int height = std::min(mPreview->getTextureHeight(), size.height); float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor(); mPreview->setViewport(int(width*scalingFactor), int(height*scalingFactor)); mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, width*scalingFactor/float(mPreview->getTextureWidth()), height*scalingFactor/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 const std::string& type = ptr.getTypeName(); bool isBook = type == typeid(ESM::Book).name(); if (!isBook && type != typeid(ESM::Ingredient).name() && type != typeid(ESM::Repair).name()) 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::shared_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.getTypeName() == typeid(ESM::Potion).name() || ptr.getTypeName() == typeid(ESM::Ingredient).name()) && 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) { // convert to OpenGL lower-left origin y = (mAvatarImage->getHeight()-1) - y; // Scale coordinates float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor(); x = static_cast(x*scalingFactor); y = static_cast(y*scalingFactor); int slot = mPreview->getSlotSelected (x, 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 std::string type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) && (type != typeid(ESM::Armor).name()) && (type != typeid(ESM::Book).name()) && (type != typeid(ESM::Clothing).name()) && (type != typeid(ESM::Ingredient).name()) && (type != typeid(ESM::Light).name()) && (type != typeid(ESM::Miscellaneous).name()) && (type != typeid(ESM::Lockpick).name()) && (type != typeid(ESM::Probe).name()) && (type != typeid(ESM::Repair).name()) && (type != typeid(ESM::Weapon).name()) && (type != typeid(ESM::Potion).name())) 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; 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(); } } openmw-openmw-0.47.0/apps/openmw/mwgui/inventorywindow.hpp000066400000000000000000000070631413061077700240240ustar00rootroot00000000000000#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(); 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.47.0/apps/openmw/mwgui/itemchargeview.cpp000066400000000000000000000154171413061077700235370ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemchargeview.hpp000066400000000000000000000035071413061077700235410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemmodel.cpp000066400000000000000000000104311413061077700225020ustar00rootroot00000000000000#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); } } openmw-openmw-0.47.0/apps/openmw/mwgui/itemmodel.hpp000066400000000000000000000067421413061077700225210ustar00rootroot00000000000000#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 (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); 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 (ItemStack item) override; /// @note Takes ownership of the passed pointer. void setSourceModel(ItemModel* sourceModel); ModelIndex mapToSource (ModelIndex index); ModelIndex mapFromSource (ModelIndex index); protected: ItemModel* mSourceModel; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/itemselection.cpp000066400000000000000000000033711413061077700233740ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemselection.hpp000066400000000000000000000020341413061077700233740ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemview.cpp000066400000000000000000000115221413061077700223560ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemview.hpp000066400000000000000000000025541413061077700223700ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/itemwidget.cpp000066400000000000000000000133771413061077700227010ustar00rootroot00000000000000#include "itemwidget.hpp" #include #include #include #include #include // correctIconPath #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" namespace { std::string getCountString(int count) { if (count == 1) return ""; 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"; invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) { Log(Debug::Error) << "Failed to open image: '" << invIcon << "' not found, falling back to 'default-icon.tga'"; invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); } 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 = ""; } 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.47.0/apps/openmw/mwgui/itemwidget.hpp000066400000000000000000000027131413061077700226760ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/jailscreen.cpp000066400000000000000000000105341413061077700226460ustar00rootroot00000000000000#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 for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) { spell.second.mNextWorsening += mDays * 24; } std::set skills; for (int day=0; day& 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) { 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.47.0/apps/openmw/mwgui/jailscreen.hpp000066400000000000000000000011671413061077700226550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/journalbooks.cpp000066400000000000000000000237551413061077700232500ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/journalbooks.hpp000066400000000000000000000017631413061077700232500ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/journalviewmodel.cpp000066400000000000000000000302271413061077700241160ustar00rootroot00000000000000#include "journalviewmodel.hpp" #include #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 = Misc::StringUtils::toLowerUtf8(stream.peek()); if (first != Misc::StringUtils::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.47.0/apps/openmw/mwgui/journalviewmodel.hpp000066400000000000000000000073571413061077700241330ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/journalwindow.cpp000066400000000000000000000560101413061077700234300ustar00rootroot00000000000000#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* _sender)) { getWidget (name) -> eventMouseButtonClick += newDelegate(this, Handler); } void adviseKeyPress (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character)) { 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.47.0/apps/openmw/mwgui/journalwindow.hpp000066400000000000000000000013351413061077700234350ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/keyboardnavigation.cpp000066400000000000000000000217641413061077700244160ustar00rootroot00000000000000#include "keyboardnavigation.hpp" #include #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()) 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; } #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) void styleFocusedButton(MyGUI::Widget* w) { if (w) { if (MyGUI::Button* b = w->castType(false)) { b->_setWidgetState("highlighted"); } } } #endif 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) { #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) styleFocusedButton(mCurrentFocus); #endif 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) { #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) if (mCurrentFocus) { if (MyGUI::Button* b = mCurrentFocus->castType(false)) b->_setWidgetState("normal"); } #endif mCurrentFocus = focus; } #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) styleFocusedButton(mCurrentFocus); #endif } 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::min(std::max(0, index), static_cast(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.47.0/apps/openmw/mwgui/keyboardnavigation.hpp000066400000000000000000000022571413061077700244170ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/layout.cpp000066400000000000000000000043361413061077700220470ustar00rootroot00000000000000#include "layout.hpp" #include #include #include #include #include namespace MWGui { void Layout::initialise(const std::string& _layout, MyGUI::Widget* _parent) { const std::string MAIN_WINDOW = "_Main"; mLayoutName = _layout; if (mLayoutName.empty()) mMainWidget = _parent; else { mPrefix = MyGUI::utility::toString(this, "_"); mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); const std::string main_name = mPrefix + MAIN_WINDOW; for (MyGUI::Widget* widget : mListWindowRoot) { if (widget->getName() == main_name) { mMainWidget = widget; break; } } 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(const std::string &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(const std::string &_name) { for (MyGUI::Widget* widget : mListWindowRoot) { MyGUI::Widget* find = widget->findWidget(mPrefix + _name); if (nullptr != find) { return find; } } MYGUI_EXCEPT("widget name '" << _name << "' in layout '" << mLayoutName << "' not found."); } } openmw-openmw-0.47.0/apps/openmw/mwgui/layout.hpp000066400000000000000000000035241413061077700220520ustar00rootroot00000000000000#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(const std::string & _layout, MyGUI::Widget* _parent = nullptr) : mMainWidget(nullptr) { initialise(_layout, _parent); } virtual ~Layout() { try { shutdown(); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } MyGUI::Widget* getWidget(const std::string& _name); template void getWidget(T * & _widget, const std::string & _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(const std::string & _layout, MyGUI::Widget* _parent = nullptr); void shutdown(); public: void setCoord(int x, int y, int w, int h); virtual void setVisible(bool b); void setText(const std::string& 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.47.0/apps/openmw/mwgui/levelupdialog.cpp000066400000000000000000000252621413061077700233670ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/levelupdialog.hpp000066400000000000000000000021701413061077700233650ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/loadingscreen.cpp000066400000000000000000000332761413061077700233540ustar00rootroot00000000000000#include "loadingscreen.hpp" #include #include #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) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); getWidget(mLoadingBox, "LoadingBox"); mProgressBar->setScrollViewPage(1); mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); mSceneImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Scene"); findSplashScreens(); } LoadingScreen::~LoadingScreen() { } void LoadingScreen::findSplashScreens() { const std::map& index = mResourceSystem->getVFS()->getIndex(); std::string pattern = "Splash/"; mResourceSystem->getVFS()->normalizeFilename(pattern); /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; auto found = index.lower_bound(pattern); while (found != index.end()) { const std::string& name = found->first; if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos) { for(auto const& extension: supported_extensions) { if (name.compare(pos, name.size() - pos, extension) == 0) { mSplashScreens.push_back(found->first); break; /* based on priority */ } } } } else break; ++found; } 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); mBackgroundImage->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(); //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; 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"); mBackgroundImage->setVisible(true); mBackgroundImage->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->setInternalFormat(GL_RGB); mTexture->setResizeNonPowerOfTwoHint(false); } if (!mGuiTexture.get()) { mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); } if (!mCopyFramebufferToTextureCallback) { mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); } #if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); #else mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); #endif mCopyFramebufferToTextureCallback->reset(); mBackgroundImage->setBackgroundImage(""); mBackgroundImage->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.47.0/apps/openmw/mwgui/loadingscreen.hpp000066400000000000000000000044101413061077700233450ustar00rootroot00000000000000#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* mBackgroundImage; 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.47.0/apps/openmw/mwgui/mainmenu.cpp000066400000000000000000000253311413061077700223410ustar00rootroot00000000000000#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, "Menu"); mVideoBackground->setImageTexture("black"); mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); 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, "Menu"); 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.47.0/apps/openmw/mwgui/mainmenu.hpp000066400000000000000000000025301413061077700223420ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/mapwindow.cpp000066400000000000000000001215631413061077700225410ustar00rootroot00000000000000#include "mapwindow.hpp" #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 "../mwrender/globalmap.hpp" #include "../mwrender/localmap.hpp" #include "confirmationdialog.hpp" #include "tooltips.hpp" namespace { const int cellSize = Constants::CellSizeInUnits; 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); } }; } 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(0) , 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) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); mCellDistance = Constants::CellGridRadius; 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() { for (int mx=0; mxsetImageTexture(""); entry.mFogTexture.reset(); continue; } } } redraw(); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) { MyGUI::IntPoint widgetPos; // normalized cell coordinates float nX,nY; if (!mInterior) { int cellX, cellY; MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); nX = (worldX - cellSize * cellX) / cellSize; // Image space is -Y up, cells are Y up nY = 1 - (worldY - cellSize * cellY) / cellSize; float cellDx = static_cast(cellX - mCurX); float cellDy = static_cast(cellY - mCurY); markerPos.cellX = cellX; markerPos.cellY = cellY; widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + cellDx) * mMapWidgetSize), static_cast(nY * mMapWidgetSize + (mCellDistance - cellDy) * mMapWidgetSize)); } else { int cellX, cellY; osg::Vec2f worldPos (worldX, worldY); mLocalMapRender->worldToInteriorMapPosition(worldPos, nX, nY, cellX, cellY); markerPos.cellX = cellX; markerPos.cellY = cellY; // Image space is -Y up, cells are Y up widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + (cellX - mCurX)) * mMapWidgetSize), static_cast(nY * mMapWidgetSize + (mCellDistance - (cellY - mCurY)) * mMapWidgetSize)); } markerPos.nX = nX; markerPos.nY = nY; return widgetPos; } 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); MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 8, widgetPos.top - 8, 16, 16); MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", widgetCoord, 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 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; updateMagicMarkers(); updateCustomMarkers(); } void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) { mLocalMapRender->requestMap(cell); } void LocalMapBase::redraw() { // Redraw children in proper order mLocalMap->getParent()->_updateChilds(); } void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { MyGUI::IntPoint pos(static_cast(mMapWidgetSize * mCellDistance + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize * mCellDistance + ny*mMapWidgetSize - 16)); pos.left += (cellX - mCurX) * mMapWidgetSize; pos.top -= (cellY - mCurY) * mMapWidgetSize; if (pos != mCompass->getPosition()) { notifyPlayerUpdate (); mCompass->setPosition(pos); MyGUI::IntPoint middle (pos.left+16, pos.top+16); MyGUI::IntCoord viewsize = mLocalMap->getCoord(); MyGUI::IntPoint viewOffset((viewsize.width / 2) - middle.left, (viewsize.height / 2) - middle.top); mLocalMap->setViewOffset(viewOffset); } } 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"; } int counter = 0; for (const MWWorld::Ptr& ptr : markers) { const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, 8, 8); ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", widgetCoord, MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); markerWidget->setImageCoord(MyGUI::IntCoord(0,0,8,8)); markerWidget->setNeedMouseFocus(false); 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.reset(new osgMyGUI::OSGTexture(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.reset(new osgMyGUI::OSGTexture("", nullptr)); } if (!entry.mFogTexture && mFogOfWarToggled && mFogOfWarEnabled) { osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(entry.mCellX, entry.mCellY); if (tex) { entry.mFogTexture.reset(new osgMyGUI::OSGTexture(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.reset(new osgMyGUI::OSGTexture("", nullptr)); } needRedraw = true; } } if (needRedraw) redraw(); } void LocalMapBase::updateDoorMarkers() { // clear all previous door markers for (MyGUI::Widget* widget : mDoorMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mDoorMarkerWidgets.clear(); MWBase::World* world = MWBase::Environment::get().getWorld(); // Retrieve the door markers we want to show std::vector doors; if (mInterior) { MWWorld::CellStore* cell = world->getInterior (mPrefix); world->getDoorMarkers(cell, doors); } else { for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) { for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) { MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); world->getDoorMarkers(cell, doors); } } } // Create a widget for each marker int counter = 0; 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); MarkerUserData data (mLocalMapRender); data.notes = destNotes; data.caption = marker.name; MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, 8, 8); ++counter; MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", widgetCoord, 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); doorMarkerCreated(markerWidget); mDoorMarkerWidgets.push_back(markerWidget); } } 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::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, 8, 8); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", widgetCoord, MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setNeedMouseFocus(false); mMagicMarkerWidgets.push_back(markerWidget); } redraw(); } // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) , mGlobalMapImage(nullptr) , mGlobalMapOverlay(nullptr) , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { 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); 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); LocalMapBase::init(mLocalMap, mPlayerArrowLocal); 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(); int x = int(widgetPos.left/float(mMapWidgetSize))-mCellDistance; int y = (int(widgetPos.top/float(mMapWidgetSize))-mCellDistance)*-1; float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); 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::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(); mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); } MapWindow::~MapWindow() { delete mGlobalMapRender; } void MapWindow::setCellName(const std::string& cellName) { setTitle("#{sCell=" + cellName + "}"); } void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { CellId cell; cell.first = x; cell.second = y; if (mMarkers.insert(cell).second) { float worldX, worldY; mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY); int markerSize = 12; int offset = mGlobalMapRender->getCellSize()/2 - markerSize/2; MyGUI::IntCoord widgetCoord( static_cast(worldX * mGlobalMapRender->getWidth()+offset), static_cast(worldY * mGlobalMapRender->getHeight() + offset), markerSize, markerSize); MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", widgetCoord, MyGUI::Align::Default); 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); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget; } } 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"); } } void MapWindow::updateCustomMarkers() { LocalMapBase::updateCustomMarkers(); for (auto& widgetPair : mGlobalMapMarkers) { int x = widgetPair.first.first; int y = widgetPair.first.second; MyGUI::Widget* markerWidget = widgetPair.second; setGlobalMapMarkerTooltip(markerWidget, x, 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) 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}"); if (mGlobal) globalMapUpdatePlayer (); } 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::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y); x *= mGlobalMapRender->getWidth(); y *= mGlobalMapRender->getHeight(); mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(static_cast(x - 16), static_cast(y - 16))); // set the view offset so that player is in the center MyGUI::IntSize viewsize = mGlobalMap->getSize(); MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - x), static_cast(viewsize.height *0.5 - y)); mGlobalMap->setViewOffset(viewoffs); } 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.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(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.second); mGlobalMapMarkers.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); } void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); } // ------------------------------------------------------------------- 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.47.0/apps/openmw/mwgui/mapwindow.hpp000066400000000000000000000204701413061077700225410ustar00rootroot00000000000000#ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H #include #include #include "windowpinnablebase.hpp" #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); 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: MWRender::LocalMap* mLocalMapRender; int mCurX, mCurY; 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::shared_ptr mMapTexture; std::shared_ptr mFogTexture; int mCellX; int mCellY; }; std::vector mMaps; // Keep track of created marker widgets, just to easily remove them later. std::vector mDoorMarkerWidgets; std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; virtual void updateCustomMarkers(); void applyFogOfWar(); MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerUserData& markerPos); virtual void notifyPlayerUpdate() {} 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 mMarkerUpdateTimer; float mLastDirectionX; float mLastDirectionY; private: void updateDoorMarkers(); bool mNeedDoorMarkersUpdate; }; 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); 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 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); 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; MWRender::GlobalMap* mGlobalMapRender; std::map, MyGUI::Widget*> mGlobalMapMarkers; EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; void onPinToggled() override; void onTitleDoubleClicked() override; void doorMarkerCreated(MyGUI::Widget* marker) override; void customMarkerCreated(MyGUI::Widget *marker) override; void notifyPlayerUpdate() override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/merchantrepair.cpp000066400000000000000000000127671413061077700235450ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/merchantrepair.hpp000066400000000000000000000011561413061077700235400ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/messagebox.cpp000066400000000000000000000316721413061077700226720ustar00rootroot00000000000000#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" #undef MessageBox 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; 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; } int MessageBoxManager::readPressedButton (bool reset) { int pressed = mLastButtonPressed; if (reset) mLastButtonPressed = -1; return pressed; } 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; } 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 + "}"), button->getCaption())) { 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.47.0/apps/openmw/mwgui/messagebox.hpp000066400000000000000000000057731413061077700227020ustar00rootroot00000000000000#ifndef MWGUI_MESSAGE_BOX_H #define MWGUI_MESSAGE_BOX_H #include "windowbase.hpp" #undef MessageBox 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(); } private: std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; float mMessageBoxSpeed; int mLastButtonPressed; }; class MessageBox : public Layout { public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); void setMessage (const std::string& message); int getHeight (); void update (int height); float mCurrentTime; float mMaxTime; protected: MessageBoxManager& mMessageBoxManager; const 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.47.0/apps/openmw/mwgui/mode.hpp000066400000000000000000000023251413061077700214570ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/pickpocketitemmodel.cpp000066400000000000000000000110761413061077700245650ustar00rootroot00000000000000#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" 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) { for (size_t i = 0; igetItemCount(); ++i) { if (Misc::Rng::roll0to99() > 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.47.0/apps/openmw/mwgui/pickpocketitemmodel.hpp000066400000000000000000000021231413061077700245630ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/quickkeysmenu.cpp000066400000000000000000000540541413061077700234310ustar00rootroot00000000000000#include "quickkeysmenu.hpp" #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; } void QuickKeysMenu::onOpen() { WindowBase::onOpen(); MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); // Check if quick keys are still valid for (int i=0; i<10; ++i) { switch (mKey[i].type) { case Type_Unassigned: case Type_HandToHand: case Type_Magic: break; case Type_Item: case Type_MagicItem: { MWWorld::Ptr item = *mKey[i].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[i].id); if (item) mKey[i].button->setUserData(MWWorld::Ptr(item)); break; } } } } } 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 = ""; key->name = ""; 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 = MWBase::Environment::get().getWindowManager()->correctIconPath(path); 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); // 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 && key->type != Type_Item) { return; } else if (isDelayNeeded && key->type != Type_Item) { 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) { bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); // delay weapon switching if player is busy if (isDelayNeeded && (isWeapon || isTool)) { mActivated = key; return; } else if (isReturnNeeded && (isWeapon || isTool)) { return; } 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.47.0/apps/openmw/mwgui/quickkeysmenu.hpp000066400000000000000000000056771413061077700234450ustar00rootroot00000000000000#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 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); 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.47.0/apps/openmw/mwgui/race.cpp000066400000000000000000000353721413061077700214500ustar00rootroot00000000000000#include "race.hpp" #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"); 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.reset(new MWRender::RaceSelectionPreview(mParent, mResourceSystem)); mPreview->rebuild(); mPreview->setAngle (mCurrentAngle); mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); 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::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 = 18; 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))); 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 = 18; MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18); 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.47.0/apps/openmw/mwgui/race.hpp000066400000000000000000000060131413061077700214430ustar00rootroot00000000000000#ifndef MWGUI_RACE_H #define MWGUI_RACE_H #include #include "windowbase.hpp" #include 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 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.47.0/apps/openmw/mwgui/recharge.cpp000066400000000000000000000076161413061077700223160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/recharge.hpp000066400000000000000000000016071413061077700223150ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/referenceinterface.cpp000066400000000000000000000007061413061077700243460ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/referenceinterface.hpp000066400000000000000000000017071413061077700243550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/repair.cpp000066400000000000000000000102221413061077700220030ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/repair.hpp000066400000000000000000000016241413061077700220160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/resourceskin.cpp000066400000000000000000000057121413061077700232450ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/resourceskin.hpp000066400000000000000000000005501413061077700232450ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/review.cpp000066400000000000000000000452371413061077700220400ustar00rootroot00000000000000#include "review.hpp" #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.47.0/apps/openmw/mwgui/review.hpp000066400000000000000000000070131413061077700220330ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/savegamedialog.cpp000066400000000000000000000424771413061077700235120ustar00rootroot00000000000000#include "savegamedialog.hpp" #include #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"); getWidget(mSpacer, "Spacer"); 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->setCaption("Select Character ..."); fillSaveList(); } void SaveGameDialog::setLoadOrSave(bool load) { mSaving = !load; mSaveNameEdit->setVisible(!load); mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); mCharacterSelection->setVisible(load); mSpacer->setUserString("Hidden", load ? "false" : "true"); 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" << "Time played: " << 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->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.reset(new osgMyGUI::OSGTexture(texture)); mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } } openmw-openmw-0.47.0/apps/openmw/mwgui/savegamedialog.hpp000066400000000000000000000040501413061077700235000ustar00rootroot00000000000000#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; MyGUI::Widget* mSpacer; const MWState::Character* mCurrentCharacter; const MWState::Slot* mCurrentSlot; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/screenfader.cpp000066400000000000000000000123571413061077700230150ustar00rootroot00000000000000#include "screenfader.hpp" #include #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); mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); 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.47.0/apps/openmw/mwgui/screenfader.hpp000066400000000000000000000032551413061077700230170ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/scrollwindow.cpp000066400000000000000000000071631413061077700232610ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/scrollwindow.hpp000066400000000000000000000020001413061077700232470ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/settingswindow.cpp000066400000000000000000000716211413061077700236230ustar00rootroot00000000000000#include "settingswindow.hpp" #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 "Trilinear"; if (val == "nearest") return "Bilinear"; if (val != "none") Log(Debug::Warning) << "Warning: Invalid texture mipmap option: "<< val; return "Other"; } 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::max(min, std::min(value, 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) { 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(mFullscreenButton, "FullscreenButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyBox, "AnisotropyBox"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); #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); mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); 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->setCaption(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 = Settings::Manager::getInt("reflection detail", "Water"); waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); updateMaxLightsComboBox(mMaxLights); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); } 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 = std::min((unsigned int)5, (unsigned int)pos); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; std::string message = "This change requires a restart to take effect."; MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, {"#{sOK}"}, true); Settings::Manager::setString("lighting method", "Shaders", _sender->getItemNameAt(pos)); 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}"}; std::string message = "Resets to default values, would you like to continue? Changes to lighting method will require a restart."; MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, 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}]); mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(Settings::Manager::mDefaultSettings[{"Shaders", "lighting method"}])); 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 (_sender == mFullscreenButton) { // 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(!newState); } 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 = SceneUtil::LightManager::getLightingMethodString(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(SceneUtil::LightManager::getLightingMethodString(method)); } mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); } void SettingsWindow::layoutControlsBox() { const int h = 18; 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); } 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(); resetScrollbars(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) { layoutControlsBox(); } void SettingsWindow::resetScrollbars() { mResolutionList->setScrollPosition(0); mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0)); } } openmw-openmw-0.47.0/apps/openmw/mwgui/settingswindow.hpp000066400000000000000000000056461413061077700236340ustar00rootroot00000000000000#ifndef MWGUI_SETTINGS_H #define MWGUI_SETTINGS_H #include "windowbase.hpp" namespace MWGui { class SettingsWindow : public WindowBase { public: SettingsWindow(); void onOpen() override; void updateControlsBox(); void updateLightSettings(); void onResChange(int, int) override { center(); } protected: MyGUI::TabControl* mSettingsTab; MyGUI::Button* mOkButton; // graphics MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::Widget* mAnisotropyBox; MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mMaxLights; MyGUI::ComboBox* mLightingMethodButton; MyGUI::Button* mLightsResetButton; // 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 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 onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightsResetButtonClicked(MyGUI::Widget* _sender); void onMaxLightsChanged(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 apply(); void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); private: void resetScrollbars(); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/sortfilteritemmodel.cpp000066400000000000000000000343571413061077700246350ustar00rootroot00000000000000#include "sortfilteritemmodel.hpp" #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 { bool compareType(const std::string& type1, const std::string& type2) { // this defines the sorting order of types. types that are first in the vector appear before other types. std::vector mapping; mapping.emplace_back(typeid(ESM::Weapon).name() ); mapping.emplace_back(typeid(ESM::Armor).name() ); mapping.emplace_back(typeid(ESM::Clothing).name() ); mapping.emplace_back(typeid(ESM::Potion).name() ); mapping.emplace_back(typeid(ESM::Ingredient).name() ); mapping.emplace_back(typeid(ESM::Apparatus).name() ); mapping.emplace_back(typeid(ESM::Book).name() ); mapping.emplace_back(typeid(ESM::Light).name() ); mapping.emplace_back(typeid(ESM::Miscellaneous).name() ); mapping.emplace_back(typeid(ESM::Lockpick).name() ); mapping.emplace_back(typeid(ESM::Repair).name() ); mapping.emplace_back(typeid(ESM::Probe).name() ); assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), 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 std::string leftName = left.mBase.getTypeName(); std::string rightName = right.mBase.getTypeName(); if (leftName != rightName) return compareType(leftName, rightName); // compare items by name leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); rightName = Misc::StringUtils::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; if (base.getTypeName() == typeid(ESM::Armor).name() || base.getTypeName() == typeid(ESM::Clothing).name()) category = Category_Apparel; else if (base.getTypeName() == typeid(ESM::Weapon).name()) category = Category_Weapon; else if (base.getTypeName() == typeid(ESM::Ingredient).name() || base.getTypeName() == typeid(ESM::Potion).name()) category = Category_Magic; else if (base.getTypeName() == typeid(ESM::Miscellaneous).name() || base.getTypeName() == typeid(ESM::Ingredient).name() || base.getTypeName() == typeid(ESM::Repair).name() || base.getTypeName() == typeid(ESM::Lockpick).name() || base.getTypeName() == typeid(ESM::Light).name() || base.getTypeName() == typeid(ESM::Apparatus).name() || base.getTypeName() == typeid(ESM::Book).name() || base.getTypeName() == typeid(ESM::Probe).name()) category = Category_Misc; if (item.mFlags & ItemStack::Flag_Enchanted) category |= Category_Magic; if (!(category & mCategory)) return false; if (mFilter & Filter_OnlyIngredients) { if (base.getTypeName() != typeid(ESM::Ingredient).name()) 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 = Misc::StringUtils::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 = Misc::StringUtils::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.getTypeName() != typeid(ESM::Miscellaneous).name() || base.getCellRef().getSoul() == "" || !MWBase::Environment::get().getWorld()->getStore().get().search(base.getCellRef().getSoul()))) return false; if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name())) return false; if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted || (base.getTypeName() != typeid(ESM::Armor).name() && base.getTypeName() != typeid(ESM::Clothing).name() && base.getTypeName() != typeid(ESM::Weapon).name() && base.getTypeName() != typeid(ESM::Book).name()))) return false; if ((mFilter & Filter_OnlyEnchantable) && base.getTypeName() == typeid(ESM::Book).name() && !base.get()->mBase->mData.mIsScroll) return false; if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) { std::shared_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.getTypeName() != typeid(ESM::Weapon).name() && base.getTypeName() != typeid(ESM::Armor).name()))) 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 = Misc::StringUtils::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 = Misc::StringUtils::lowerCaseUtf8(filter); } void SortFilterItemModel::setEffectFilter (const std::string& filter) { mEffectFilter = Misc::StringUtils::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.47.0/apps/openmw/mwgui/sortfilteritemmodel.hpp000066400000000000000000000042721413061077700246330ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/soulgemdialog.cpp000066400000000000000000000016241413061077700233620ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/soulgemdialog.hpp000066400000000000000000000007341413061077700233700ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellbuyingwindow.cpp000066400000000000000000000170641413061077700243210ustar00rootroot00000000000000#include "spellbuyingwindow.hpp" #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/creaturestats.hpp" #include "../mwmechanics/actorutil.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 (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) { const ESM::Spell* spell = iter->first; 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(iter->first->mId)) continue; spellsToSort.push_back(iter->first); } 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.47.0/apps/openmw/mwgui/spellbuyingwindow.hpp000066400000000000000000000027041413061077700243210ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellcreationdialog.cpp000066400000000000000000000665031413061077700245620ustar00rootroot00000000000000#include "spellcreationdialog.hpp" #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/esmstore.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/spellutil.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; bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (!allowSelf && !allowTouch && !allowTarget) return; // TODO: Show an error message popup? 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(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); 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)); 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 (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = it->first; // 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); 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.47.0/apps/openmw/mwgui/spellcreationdialog.hpp000066400000000000000000000112431413061077700245560ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellicons.cpp000066400000000000000000000210631413061077700227010ustar00rootroot00000000000000#include "spellicons.hpp" #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 EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) { MagicEffectInfo newEffectSource; newEffectSource.mKey = key; newEffectSource.mMagnitude = static_cast(magnitude); newEffectSource.mPermanent = mIsPermanent; newEffectSource.mRemainingTime = remainingTime; newEffectSource.mSource = sourceName; newEffectSource.mTotalTime = totalTime; mEffectSources[key.mId].push_back(newEffectSource); } void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { // TODO: Tracking add/remove/expire would be better than force updating every frame MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); EffectSourceVisitor visitor; // permanent item enchantments & permanent spells visitor.mIsPermanent = true; MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.visitEffectSources(visitor); stats.getSpells().visitEffectSources(visitor); // now add lasting effects visitor.mIsPermanent = false; stats.getActiveSpells().visitEffectSources(visitor); std::map >& effects = visitor.mEffectSources; int w=2; for (auto& effectInfoPair : effects) { const int effectId = effectInfoPair.first; 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(); std::vector& effectInfos = effectInfoPair.second; 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(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); 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.47.0/apps/openmw/mwgui/spellicons.hpp000066400000000000000000000027731413061077700227150ustar00rootroot00000000000000#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 EffectSourceVisitor : public MWMechanics::EffectSourceVisitor { public: bool mIsPermanent; std::map > mEffectSources; virtual ~EffectSourceVisitor() {} void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override; }; class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: std::map mWidgetMap; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/spellmodel.cpp000066400000000000000000000165071413061077700226750ustar00rootroot00000000000000#include "spellmodel.hpp" #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().search(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 = Misc::StringUtils::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 = Misc::StringUtils::lowerCaseUtf8(mFilter); for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; std::string name = Misc::StringUtils::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(spell->mData.mCost); 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 = Misc::StringUtils::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.47.0/apps/openmw/mwgui/spellmodel.hpp000066400000000000000000000031421413061077700226710ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellview.cpp000066400000000000000000000253711413061077700225460ustar00rootroot00000000000000#include "spellview.hpp" #include #include #include #include #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 = 18; 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.47.0/apps/openmw/mwgui/spellview.hpp000066400000000000000000000051121413061077700225420ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellwindow.cpp000066400000000000000000000225631413061077700231030ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/spellwindow.hpp000066400000000000000000000023421413061077700231010ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/statswatcher.cpp000066400000000000000000000145211413061077700232430ustar00rootroot00000000000000#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" 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) { std::stringstream attrname; attrname << "AttribVal"<<(i+1); mWatchedAttributes[i] = stats.getAttribute(i); setValue(attrname.str(), 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.47.0/apps/openmw/mwgui/statswatcher.hpp000066400000000000000000000045151413061077700232520ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/statswindow.cpp000066400000000000000000000721121413061077700231150ustar00rootroot00000000000000#include "statswindow.hpp" #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()); // 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); // 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)); } 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); } levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); 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 { int rank = factionPair.second; rank = std::max(0, std::min(9, rank)); 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.47.0/apps/openmw/mwgui/statswindow.hpp000066400000000000000000000067721413061077700231330ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/textcolours.cpp000066400000000000000000000021511413061077700231160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/textcolours.hpp000066400000000000000000000013111413061077700231200ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/textinput.cpp000066400000000000000000000044741413061077700226010ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/textinput.hpp000066400000000000000000000014371413061077700226020ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/timeadvancer.cpp000066400000000000000000000024131413061077700231660ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/timeadvancer.hpp000066400000000000000000000016071413061077700231770ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/tooltips.cpp000066400000000000000000001133271413061077700224100ustar00rootroot00000000000000#include "tooltips.hpp" #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=""; 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 int i=0; while (!focus->isUserString("ToolTipType")) { focus = focus->getParent(); if (!focus) return; ++i; } 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(spell->mData.mCost, "#{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 = ""; 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; std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); 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; effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, 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, true, 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) { 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); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); widget->setUserString("ImageTexture_BirthSignImage", MWBase::Environment::get().getWindowManager()->correctTexturePath(sign->mTexture)); 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 = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); 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.47.0/apps/openmw/mwgui/tooltips.hpp000066400000000000000000000115171413061077700224130ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/tradeitemmodel.cpp000066400000000000000000000150261413061077700235270ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/tradeitemmodel.hpp000066400000000000000000000035031413061077700235310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/tradewindow.cpp000066400000000000000000000501751413061077700230630ustar00rootroot00000000000000#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(); // 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) { // Entering a "-" sign inverts the buying/selling state mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; updateLabels(); 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 (mCurrentBalance < 0) mCurrentBalance -= 1; else mCurrentBalance += 1; updateLabels(); } void TradeWindow::onDecreaseButtonTriggered() { 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)); 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(); } } openmw-openmw-0.47.0/apps/openmw/mwgui/tradewindow.hpp000066400000000000000000000072311413061077700230630ustar00rootroot00000000000000#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; 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.47.0/apps/openmw/mwgui/trainingwindow.cpp000066400000000000000000000173521413061077700235770ustar00rootroot00000000000000#include "trainingwindow.hpp" #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(); 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*18, mTrainingOptions->getWidth()-10, 18), 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.47.0/apps/openmw/mwgui/trainingwindow.hpp000066400000000000000000000027221413061077700235770ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/travelwindow.cpp000066400000000000000000000226761413061077700232660ustar00rootroot00000000000000#include "travelwindow.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.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, 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); // 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.getTypeName() == typeid(ESM::Creature).name()) transport = mPtr.get()->mBase->getTransport(); for(unsigned int i = 0;ipositionToIndex(transport[i].mPos.pos[0], transport[i].mPos.pos[1],x,y); if (cellname == "") { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(x,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.47.0/apps/openmw/mwgui/travelwindow.hpp000066400000000000000000000020431413061077700232550ustar00rootroot00000000000000#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, ESM::Position pos, bool interior); void clearDestinations(); int mCurrentY; void updateLabels(); void onReferenceUnavailable() override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/videowidget.cpp000066400000000000000000000047551413061077700230510ustar00rootroot00000000000000#include "videowidget.hpp" #include #include #include #include #include #include #include "../mwsound/movieaudiofactory.hpp" namespace MWGui { VideoWidget::VideoWidget() : mVFS(nullptr) { mPlayer.reset(new Video::VideoPlayer()); 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(videoStream, video); osg::ref_ptr texture = mPlayer->getVideoTexture(); if (!texture) return; mTexture.reset(new osgMyGUI::OSGTexture(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.47.0/apps/openmw/mwgui/videowidget.hpp000066400000000000000000000027671413061077700230570ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/waitdialog.cpp000066400000000000000000000305251413061077700226550ustar00rootroot00000000000000#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() { return (!mTimeAdvancer.isRunning()); //Only exit if not currently waiting } void WaitDialog::clear() { mSleeping = false; 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(); int daysPassed = MWBase::Environment::get().getWorld()->getTimeStamp().getDay(); std::string formattedHour = pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"; std::string dateTimeText = Misc::StringUtils::format("%i %s (#{sDay} %i) %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); 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.47.0/apps/openmw/mwgui/waitdialog.hpp000066400000000000000000000040631413061077700226600ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/widgets.cpp000066400000000000000000000476541413061077700222120ustar00rootroot00000000000000#include "widgets.hpp" #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 { namespace 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(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon)); } 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.47.0/apps/openmw/mwgui/widgets.hpp000066400000000000000000000226631413061077700222100ustar00rootroot00000000000000#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(); typedef MWMechanics::Stat SpellValue; 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(); typedef MWMechanics::Stat EnchantmentValue; 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.47.0/apps/openmw/mwgui/windowbase.cpp000066400000000000000000000103341413061077700226670ustar00rootroot00000000000000#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(const std::string& 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(const std::string& parLayout) : WindowBase(parLayout) { } float BookWindowBase::adjustButton (char const * 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.47.0/apps/openmw/mwgui/windowbase.hpp000066400000000000000000000046751413061077700227070ustar00rootroot00000000000000#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(const std::string& 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) {} 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(const std::string& parLayout); protected: float adjustButton (char const * name); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/windowmanagerimp.cpp000066400000000000000000002263011413061077700241000ustar00rootroot00000000000000#include "windowmanagerimp.hpp" #include #include #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 "../mwbase/inputmanager.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 "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 "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, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, const std::string& userDataPath) : 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) , mJailScreen(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(), mScalingFactor); mGuiPlatform->initialise(resourcePath, (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.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS(), userDataPath, mScalingFactor)); mFontLoader->loadBitmapFonts(exportFonts); //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 (); ItemView::registerComponents(); ItemChargeView::registerComponents(); ItemWidget::registerComponents(); SpellView::registerComponents(); Gui::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "AutoSizedResourceSkin"); MyGUI::ResourceManager::getInstance().load("core.xml"); WindowManager::loadUserFonts(); bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI"); mKeyboardNavigation.reset(new KeyboardNavigation()); 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, "InputBlocker"); 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")); mStatsWatcher.reset(new StatsWatcher()); } void WindowManager::loadUserFonts() { mFontLoader->loadTrueTypeFonts(); } 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); ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop); mWindows.push_back(containerWindow); trackWindow(containerWindow, "container"); mGuiModeStates[GM_Container] = GuiModeState({containerWindow, 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); 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); 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 { 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 && mViewer->getCamera()->getCullMask() != disablemask) { mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); mOldCullMask = mViewer->getCamera()->getCullMask(); mViewer->getUpdateVisitor()->setTraversalMask(disablemask); mViewer->getCamera()->setCullMask(disablemask); } else if (enable && mViewer->getCamera()->getCullMask() == disablemask) { mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); mViewer->getCamera()->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); 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::staticMessageBox(const std::string& message) { mMessageBoxManager->createMessageBox(message, true); } void WindowManager::removeStaticMessageBox() { mMessageBoxManager->removeStaticMessageBox(); } 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) { 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(); 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); mDebugWindow->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 { if (!mStore) { Log(Debug::Error) << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'"; return; } const ESM::GameSetting *setting = mStore->get().find(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 == "fullscreen" || 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"), Settings::Manager::getBool("fullscreen", "Video"), Settings::Manager::getBool("window border", "Video")); } } void WindowManager::windowResized(int x, int y) { // Note: this is a side effect of resolution change or window resize. // There is no need to track these changes. Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); Settings::Manager::resetPendingChange("resolution x", "Video"); Settings::Manager::resetPendingChange("resolution y", "Video"); 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); // We should reload TrueType fonts to fit new resolution loadUserFonts(); // 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) { 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); } for (WindowBase* window : mGuiModeStates[mode].mWindows) window->setPtr(arg); mKeyboardNavigation->restoreFocus(mode); updateVisible(); } 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 = ""; 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 = ""; 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() { 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; } 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() || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } bool WindowManager::isConsoleMode() const { return mConsole && mConsole->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(); 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) { 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 (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() { #ifndef BT_NO_PROFILE mDebugWindow->setVisible(!mDebugWindow->isVisible()); #endif } 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::NoEnv); } void WindowManager::updateSpellWindow() { if (mSpellWindow) mSpellWindow->updateSpells(); } void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr &object) { mConsole->setSelectedObject(object); } std::string WindowManager::correctIconPath(const std::string& path) { return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); } std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists) { std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); if (exists) *exists = mResourceSystem->getVFS()->exists(corrected); return corrected; } std::string WindowManager::correctTexturePath(const std::string& path) { return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); } bool WindowManager::textureExists(const std::string &path) { std::string corrected = Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); return mResourceSystem->getVFS()->exists(corrected); } 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(); } } openmw-openmw-0.47.0/apps/openmw/mwgui/windowmanagerimp.hpp000066400000000000000000000437651413061077700241200ustar00rootroot00000000000000#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 "../mwbase/windowmanager.hpp" #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 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, const std::string& cacheDir, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, const std::string& localPath); virtual ~WindowManager(); /// Set the ESMStore to use for retrieving of GUI-related strings. void setStore (const MWWorld::ESMStore& store); void initUI(); void loadUserFonts() override; 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) 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; 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; /// 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; /// 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() 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 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) override; /** * 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; /// 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; // In WindowManager for now since there isn't a VFS singleton std::string correctIconPath(const std::string& path) override; std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) override; std::string correctTexturePath(const std::string& path) override; bool textureExists(const std::string& path) 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; 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; JailScreen* mJailScreen; 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; /** * 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); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwgui/windowpinnablebase.cpp000066400000000000000000000021371413061077700244020ustar00rootroot00000000000000#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.47.0/apps/openmw/mwgui/windowpinnablebase.hpp000066400000000000000000000011511413061077700244020ustar00rootroot00000000000000#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.47.0/apps/openmw/mwinput/000077500000000000000000000000001413061077700203735ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwinput/actionmanager.cpp000066400000000000000000000554031413061077700237160ustar00rootroot00000000000000#include "actionmanager.hpp" #include #include #include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.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 "actions.hpp" #include "bindingsmanager.hpp" namespace MWInput { const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out 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) , mOverencumberedMessageDelay(0.f) , mPreviewPOVDelay(0.f) , 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; mOverencumberedMessageDelay = 0.f; } // 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(); mOverencumberedMessageDelay -= dt; if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) { player.setAutoMove (false); if (mOverencumberedMessageDelay <= 0) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); mOverencumberedMessageDelay = 1.0; } } } if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) { const float switchLimit = 0.25; MWBase::World* world = MWBase::Environment::get().getWorld(); if (mBindingsManager->actionIsActive(A_TogglePOV)) { if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0) world->togglePreviewMode(true); mPreviewPOVDelay += dt; } else { //disable preview mode if (mPreviewPOVDelay > 0) world->togglePreviewMode(false); if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit) world->togglePOV(); mPreviewPOVDelay = 0.f; } } 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 { updateIdleTime(dt); } mAttemptJump = false; } bool ActionManager::isPreviewModeEnabled() { return MWBase::Environment::get().getWorld()->isPreviewModeEnabled(); } void ActionManager::resetIdleTime() { if (mTimeIdle < 0) MWBase::Environment::get().getWorld()->toggleVanityMode(false); mTimeIdle = 0.f; } void ActionManager::updateIdleTime(float dt) { static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() .find("fVanityDelay")->mValue.getFloat(); if (mTimeIdle >= 0.f) mTimeIdle += dt; if (mTimeIdle > vanityDelay) { MWBase::Environment::get().getWorld()->toggleVanityMode(true); mTimeIdle = -1.f; } } void ActionManager::executeAction(int action) { auto* inputManager = MWBase::Environment::get().getInputManager(); 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_ZoomIn: if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE); break; case A_ZoomOut: if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE); 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()->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; if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Journal && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_MainMenu && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); } else if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Journal)) { MWBase::Environment::get().getWindowManager()->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()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) { if (!checkAllowedToUseItems()) return; MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); } else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) { while (MyGUI::InputManager::getInstance().isModalAny()) { //Handle any open Modal windows MWBase::Environment::get().getWindowManager()->exitCurrentModal(); } MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); //And handle the actual main window } } 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.47.0/apps/openmw/mwinput/actionmanager.hpp000066400000000000000000000037121413061077700237170ustar00rootroot00000000000000#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(); bool isAlwaysRunActive() const { return mAlwaysRunActive; }; bool isSneaking() const { return mSneaking; }; void setAttemptJump(bool enabled) { mAttemptJump = enabled; } bool isPreviewModeEnabled(); private: void handleGuiArrowKey(int action); void updateIdleTime(float dt); BindingsManager* mBindingsManager; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation; bool mAlwaysRunActive; bool mSneaking; bool mAttemptJump; float mOverencumberedMessageDelay; float mPreviewPOVDelay; float mTimeIdle; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/actions.hpp000066400000000000000000000037241413061077700225520ustar00rootroot00000000000000#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_Last // Marker for the last item }; } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/bindingsmanager.cpp000066400000000000000000000756361413061077700242500ustar00rootroot00000000000000#include "bindingsmanager.hpp" #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 "sdlmappings.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; 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 "Screenshot"; case A_ZoomIn: return "Zoom In"; case A_ZoomOut: return "Zoom Out"; case A_ToggleHUD: return "Toggle HUD"; 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}"; 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 sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) return 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 }; 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); } 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.47.0/apps/openmw/mwinput/bindingsmanager.hpp000066400000000000000000000045701413061077700242420ustar00rootroot00000000000000#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; 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.47.0/apps/openmw/mwinput/controllermanager.cpp000066400000000000000000000364251413061077700246270ustar00rootroot00000000000000#include "controllermanager.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.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" #include "sdlmappings.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")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) , mGamepadZoom(0) , mGamepadGuiCursorEnabled(true) , mGuiCursorEnabled(true) , mJoystickLastUsed(false) , mSneakGamepadShortcut(false) , mGamepadPreviewMode(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; 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::min(std::max(deadZoneRadius, 0.0f), 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) { mGamepadPreviewMode = mActionManager->isPreviewModeEnabled(); 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) { mGamepadZoom = 0; 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)); } } if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) { if (!mBindingsManager->actionIsActive(A_TogglePOV)) mGamepadZoom = 0; if (mGamepadZoom) MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom); } return triedToMove; } void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) { if (!mJoystickEnabled || mBindingsManager->isDetectingBindingState()) return; 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 = 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().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 = 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 (mGamepadPreviewMode) // Preview Mode Gamepad Zooming { if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f; return; // Do not propagate event. } else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) { mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f; return; // Do not propagate event. } } } mBindingsManager->controllerAxisMoved(deviceID, arg); } void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) { mBindingsManager->controllerAdded(deviceID, arg); } 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; } } openmw-openmw-0.47.0/apps/openmw/mwinput/controllermanager.hpp000066400000000000000000000043251413061077700246260ustar00rootroot00000000000000#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 processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } bool joystickLastUsed() { return mJoystickLastUsed; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; } bool gamepadGuiCursorEnabled() { return mGamepadGuiCursorEnabled; } private: // Return true if GUI consumes input. bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg); bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); BindingsManager* mBindingsManager; ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; float mGamepadCursorSpeed; float mSneakToggleShortcutTimer; float mGamepadZoom; bool mGamepadGuiCursorEnabled; bool mGuiCursorEnabled; bool mJoystickLastUsed; bool mSneakGamepadShortcut; bool mGamepadPreviewMode; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/controlswitch.cpp000066400000000000000000000062111413061077700240010ustar00rootroot00000000000000#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(const std::string& key) { return mSwitches[key]; } void ControlSwitch::set(const std::string& 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 == "vanitymode") { MWBase::Environment::get().getWorld()->allowVanityMode(value); } else if (key == "playerlooking" && !value) { MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), 0.f, 0.f, 0.f); } mSwitches[key] = 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.47.0/apps/openmw/mwinput/controlswitch.hpp000066400000000000000000000012651413061077700240120ustar00rootroot00000000000000#ifndef MWINPUT_CONTROLSWITCH_H #define MWINPUT_CONTROLSWITCH_H #include #include namespace ESM { struct ControlsState; class ESMReader; class ESMWriter; } namespace Loading { class Listener; } namespace MWInput { class ControlSwitch { public: ControlSwitch(); bool get(const std::string& key); void set(const std::string& 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.47.0/apps/openmw/mwinput/inputmanagerimp.cpp000066400000000000000000000153131413061077700243020ustar00rootroot00000000000000#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 "sdlmappings.hpp" #include "sensormanager.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 = new SDLUtil::InputWrapper(window, viewer, grab); mInputWrapper->setWindowEventCallback(MWBase::Environment::get().getWindowManager()); mBindingsManager = new BindingsManager(userFile, userFileExists); mControlSwitch = new ControlSwitch(); mActionManager = new ActionManager(mBindingsManager, screenCaptureOperation, viewer, screenCaptureHandler); mKeyboardManager = new KeyboardManager(mBindingsManager); mInputWrapper->setKeyboardEventCallback(mKeyboardManager); mMouseManager = new MouseManager(mBindingsManager, mInputWrapper, window); mInputWrapper->setMouseEventCallback(mMouseManager); mControllerManager = new ControllerManager(mBindingsManager, mActionManager, mMouseManager, userControllerBindingsFile, controllerBindingsFile); mInputWrapper->setControllerEventCallback(mControllerManager); mSensorManager = new SensorManager(); mInputWrapper->setSensorEventCallback(mSensorManager); } void InputManager::clear() { // Enable all controls mControlSwitch->clear(); } InputManager::~InputManager() { delete mActionManager; delete mControllerManager; delete mKeyboardManager; delete mMouseManager; delete mSensorManager; delete mControlSwitch; delete mBindingsManager; delete mInputWrapper; } 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); MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt); } 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); mSensorManager->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); } bool InputManager::getControlSwitch(const std::string& sw) { return mControlSwitch->get(sw); } void InputManager::toggleControlSwitch(const std::string& sw, bool value) { mControlSwitch->set(sw, value); } void InputManager::resetIdleTime() { mActionManager->resetIdleTime(); } std::string InputManager::getActionDescription(int action) { return mBindingsManager->getActionDescription(action); } std::string InputManager::getActionKeyBindingName(int action) { return mBindingsManager->getActionKeyBindingName(action); } std::string InputManager::getActionControllerBindingName(int action) { return mBindingsManager->getActionControllerBindingName(action); } 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.47.0/apps/openmw/mwinput/inputmanagerimp.hpp000066400000000000000000000071241413061077700243100ustar00rootroot00000000000000#ifndef MWINPUT_MWINPUTMANAGERIMP_H #define MWINPUT_MWINPUTMANAGERIMP_H #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; /** * @brief Class that provides a high-level API for game input */ class InputManager : 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); virtual ~InputManager(); /// Clear all savegame-specific data void clear() override; void update(float dt, bool disableControls=false, 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 (const std::string& sw, bool value) override; bool getControlSwitch (const std::string& sw) override; std::string getActionDescription (int action) override; std::string getActionKeyBindingName (int action) override; std::string getActionControllerBindingName (int action) 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; 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); SDLUtil::InputWrapper* mInputWrapper; bool mControlsDisabled; ControlSwitch* mControlSwitch; ActionManager* mActionManager; BindingsManager* mBindingsManager; ControllerManager* mControllerManager; KeyboardManager* mKeyboardManager; MouseManager* mMouseManager; SensorManager* mSensorManager; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/keyboardmanager.cpp000066400000000000000000000054621413061077700242410ustar00rootroot00000000000000#include "keyboardmanager.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/player.hpp" #include "actions.hpp" #include "bindingsmanager.hpp" #include "sdlmappings.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 = 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) && (std::isprint(arg.keysym.sym) || // Don't trust isprint for symbols outside the extended ASCII range (kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); 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); input->setJoystickLastUsed(false); } void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); auto kc = sdlKeyToMyGUI(arg.keysym.sym); if (!mBindingsManager->isDetectingBindingState()) mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->keyReleased(arg); } } openmw-openmw-0.47.0/apps/openmw/mwinput/keyboardmanager.hpp000066400000000000000000000011421413061077700242350ustar00rootroot00000000000000#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.47.0/apps/openmw/mwinput/mousemanager.cpp000066400000000000000000000251641413061077700235720ustar00rootroot00000000000000#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" #include "sdlmappings.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) { 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), sdlButtonToMyGUI(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), sdlButtonToMyGUI(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) { 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), sdlButtonToMyGUI(button)); } bool MouseManager::injectMouseButtonRelease(Uint8 button) { return MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(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::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1))); mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(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.47.0/apps/openmw/mwinput/mousemanager.hpp000066400000000000000000000032521413061077700235710ustar00rootroot00000000000000#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; } 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/sdlmappings.cpp000066400000000000000000000214621413061077700234250ustar00rootroot00000000000000#include "sdlmappings.hpp" #include #include #include #include namespace MWInput { 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 sdlButtonToMyGUI(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); } void 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 } MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code) { static std::map keyMap; if (keyMap.empty()) initKeyMap(keyMap); MyGUI::KeyCode kc = MyGUI::KeyCode::None; auto foundKey = keyMap.find(code); if (foundKey != keyMap.end()) kc = foundKey->second; return kc; } } openmw-openmw-0.47.0/apps/openmw/mwinput/sdlmappings.hpp000066400000000000000000000006541413061077700234320ustar00rootroot00000000000000#ifndef MWINPUT_SDLMAPPINGS_H #define MWINPUT_SDLMAPPINGS_H #include #include #include namespace MyGUI { struct MouseButton; } namespace MWInput { std::string sdlControllerButtonToString(int button); std::string sdlControllerAxisToString(int axis); MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button); MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code); } #endif openmw-openmw-0.47.0/apps/openmw/mwinput/sensormanager.cpp000066400000000000000000000224761413061077700237560ustar00rootroot00000000000000#include "sensormanager.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" namespace MWInput { SensorManager::SensorManager() : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) , mGyroXSpeed(0.f) , mGyroYSpeed(0.f) , mGyroUpdateTimer(0.f) , mGyroHSensitivity(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) , mGyroVSensitivity(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) , mGyroHAxis(GyroscopeAxis::Minus_X) , mGyroVAxis(GyroscopeAxis::Y) , mGyroInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mGyroscope(nullptr) , mGuiCursorEnabled(true) { init(); } void SensorManager::init() { correctGyroscopeAxes(); updateSensors(); } SensorManager::~SensorManager() { if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; } } SensorManager::GyroscopeAxis SensorManager::mapGyroscopeAxis(const std::string& axis) { if (axis == "x") return GyroscopeAxis::X; else if (axis == "y") return GyroscopeAxis::Y; else if (axis == "z") return GyroscopeAxis::Z; else if (axis == "-x") return GyroscopeAxis::Minus_X; else if (axis == "-y") return GyroscopeAxis::Minus_Y; else if (axis == "-z") return GyroscopeAxis::Minus_Z; return GyroscopeAxis::Unknown; } 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. mGyroHAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro horizontal axis", "Input")); mGyroVAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro vertical axis", "Input")); SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: return; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { mGyroHAxis = GyroscopeAxis(-mGyroHAxis); mGyroVAxis = GyroscopeAxis(-mGyroVAxis); break; } case SDL_ORIENTATION_PORTRAIT: { GyroscopeAxis oldVAxis = mGyroVAxis; mGyroVAxis = mGyroHAxis; mGyroHAxis = GyroscopeAxis(-oldVAxis); break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { GyroscopeAxis oldVAxis = mGyroVAxis; mGyroVAxis = GyroscopeAxis(-mGyroHAxis); mGyroHAxis = oldVAxis; break; } } } 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; mGyroXSpeed = mGyroYSpeed = 0.f; 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; mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } } } void SensorManager::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 == "gyro horizontal sensitivity") mGyroHSensitivity = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); if (setting.first == "Input" && setting.second == "gyro vertical sensitivity") mGyroVSensitivity = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); if (setting.first == "Input" && setting.second == "enable gyroscope") init(); if (setting.first == "Input" && setting.second == "gyro horizontal axis") correctGyroscopeAxes(); if (setting.first == "Input" && setting.second == "gyro vertical axis") correctGyroscopeAxes(); if (setting.first == "Input" && setting.second == "gyro input threshold") mGyroInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); } } float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const { switch (axis) { case GyroscopeAxis::X: case GyroscopeAxis::Y: case GyroscopeAxis::Z: return std::abs(arg.data[0]) >= mGyroInputThreshold ? arg.data[axis-1] : 0.f; case GyroscopeAxis::Minus_X: case GyroscopeAxis::Minus_Y: case GyroscopeAxis::Minus_Z: return std::abs(arg.data[0]) >= mGyroInputThreshold ? -arg.data[std::abs(axis)-1] : 0.f; default: return 0.f; } } 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: { mGyroXSpeed = getGyroAxisSpeed(mGyroHAxis, arg); mGyroYSpeed = getGyroAxisSpeed(mGyroVAxis, arg); mGyroUpdateTimer = 0.f; break; } default: break; } } void SensorManager::update(float dt) { if (mGyroXSpeed == 0.f && mGyroYSpeed == 0.f) return; 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. mGyroXSpeed = 0.f; mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; return; } mGyroUpdateTimer += dt; if (!mGuiCursorEnabled) { float rot[3]; rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); rot[1] = 0.0f; rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); // 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(); } } } openmw-openmw-0.47.0/apps/openmw/mwinput/sensormanager.hpp000066400000000000000000000030621413061077700237510ustar00rootroot00000000000000#ifndef MWINPUT_MWSENSORMANAGER_H #define MWINPUT_MWSENSORMANAGER_H #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); 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 }; void updateSensors(); void correctGyroscopeAxes(); GyroscopeAxis mapGyroscopeAxis(const std::string& axis); float getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const; bool mInvertX; bool mInvertY; float mGyroXSpeed; float mGyroYSpeed; float mGyroUpdateTimer; float mGyroHSensitivity; float mGyroVSensitivity; GyroscopeAxis mGyroHAxis; GyroscopeAxis mGyroVAxis; float mGyroInputThreshold; SDL_Sensor* mGyroscope; bool mGuiCursorEnabled; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/000077500000000000000000000000001413061077700211665ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwmechanics/activespells.cpp000066400000000000000000000263731413061077700244030ustar00rootroot00000000000000#include "activespells.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWMechanics { void ActiveSpells::update(float duration) const { bool rebuild = false; // Erase no longer active spells and effects if (duration > 0) { TContainer::iterator iter (mSpells.begin()); while (iter!=mSpells.end()) { if (!timeToExpire (iter)) { mSpells.erase (iter++); rebuild = true; } else { bool interrupt = false; std::vector& effects = iter->second.mEffects; for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) { if (effectIt->mTimeLeft <= 0) { rebuild = true; // Note: it we expire a Corprus effect, we should remove the whole spell. if (effectIt->mEffectId == ESM::MagicEffect::Corprus) { iter = mSpells.erase (iter); interrupt = true; break; } effectIt = effects.erase(effectIt); } else { effectIt->mTimeLeft -= duration; ++effectIt; } } if (!interrupt) ++iter; } } } if (mSpellsChanged) { mSpellsChanged = false; rebuild = true; } if (rebuild) rebuildEffects(); } void ActiveSpells::rebuildEffects() const { mEffects = MagicEffects(); for (TIterator iter (begin()); iter!=end(); ++iter) { const std::vector& effects = iter->second.mEffects; for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { if (effectIt->mTimeLeft > 0) mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); } } } ActiveSpells::ActiveSpells() : mSpellsChanged (false) {} const MagicEffects& ActiveSpells::getMagicEffects() const { update(0.f); return mEffects; } ActiveSpells::TIterator ActiveSpells::begin() const { return mSpells.begin(); } ActiveSpells::TIterator ActiveSpells::end() const { return mSpells.end(); } double ActiveSpells::timeToExpire (const TIterator& iterator) const { const std::vector& effects = iterator->second.mEffects; float duration = 0; for (std::vector::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { if (iter->mTimeLeft > duration) duration = iter->mTimeLeft; } if (duration < 0) return 0; return duration; } bool ActiveSpells::isSpellActive(const std::string& id) const { for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { if (Misc::StringUtils::ciEqual(iter->first, id)) return true; } return false; } const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const { return mSpells; } void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, const std::string &displayName, int casterActorId) { TContainer::iterator it(mSpells.find(id)); ActiveSpellParams params; params.mEffects = effects; params.mDisplayName = displayName; params.mCasterActorId = casterActorId; if (it == end() || stack) { mSpells.insert(std::make_pair(id, params)); } else { // addSpell() is called with effects for a range. // but a spell may have effects with different ranges (e.g. Touch & Target) // so, if we see new effects for same spell assume additional // spell effects and add to existing effects of spell mergeEffects(params.mEffects, it->second.mEffects); it->second = params; } mSpellsChanged = true; } void ActiveSpells::mergeEffects(std::vector& addTo, const std::vector& from) { for (std::vector::const_iterator effect(from.begin()); effect != from.end(); ++effect) { // if effect is not in addTo, add it bool missing = true; for (std::vector::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter) { if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg)) { missing = false; break; } } if (missing) { addTo.push_back(*effect); } } } void ActiveSpells::removeEffects(const std::string &id) { for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) { if (spell->first == id) { spell->second.mEffects.clear(); mSpellsChanged = true; } } } void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TContainer::const_iterator it = begin(); it != end(); ++it) { for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end(); ++effectIt) { std::string name = it->second.mDisplayName; float magnitude = effectIt->mMagnitude; if (magnitude) visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); } } } void ActiveSpells::purgeAll(float chance, bool spellOnly) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) { const std::string spellId = it->first; // if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. if (spellOnly) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) { ++it; continue; } } if (Misc::Rng::roll0to99() < chance) mSpells.erase(it++); else ++it; } mSpellsChanged = true; } void ActiveSpells::purgeEffect(short effectId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { if (effectIt->mEffectId == effectId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; } } mSpellsChanged = true; } void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex)) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; } } mSpellsChanged = true; } void ActiveSpells::purge(int casterActorId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { if (it->second.mCasterActorId == casterActorId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; } } mSpellsChanged = true; } void ActiveSpells::purgeCorprusDisease() { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { bool hasCorprusEffect = false; for (std::vector::iterator effectIt = iter->second.mEffects.begin(); effectIt != iter->second.mEffects.end();++effectIt) { if (effectIt->mEffectId == ESM::MagicEffect::Corprus) { hasCorprusEffect = true; break; } } if (hasCorprusEffect) { mSpells.erase(iter++); mSpellsChanged = true; } else ++iter; } } void ActiveSpells::clear() { mSpells.clear(); mSpellsChanged = true; } void ActiveSpells::writeState(ESM::ActiveSpells &state) const { for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) { // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp ESM::ActiveSpells::ActiveSpellParams params; params.mEffects = it->second.mEffects; params.mCasterActorId = it->second.mCasterActorId; params.mDisplayName = it->second.mDisplayName; state.mSpells.insert (std::make_pair(it->first, params)); } } void ActiveSpells::readState(const ESM::ActiveSpells &state) { for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp ActiveSpellParams params; params.mEffects = it->second.mEffects; params.mCasterActorId = it->second.mCasterActorId; params.mDisplayName = it->second.mDisplayName; mSpells.insert (std::make_pair(it->first, params)); mSpellsChanged = true; } } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/activespells.hpp000066400000000000000000000066141413061077700244040ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H #define GAME_MWMECHANICS_ACTIVESPELLS_H #include #include #include #include #include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" namespace MWMechanics { /// \brief Lasting spell effects /// /// \note The name of this class is slightly misleading, since it also handels lasting potion /// effects. class ActiveSpells { public: typedef ESM::ActiveEffect ActiveEffect; struct ActiveSpellParams { std::vector mEffects; MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; // The caster that inflicted this spell on us int mCasterActorId; }; typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; TIterator begin() const; TIterator end() const; void update(float duration) const; private: mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; void rebuildEffects() const; /// Add any effects that are in "from" and not in "addTo" to "addTo" void mergeEffects(std::vector& addTo, const std::vector& from); double timeToExpire (const TIterator& iterator) const; ///< Returns time (in in-game hours) until the spell pointed to by \a iterator /// expires. const TContainer& getActiveSpells() const; public: ActiveSpells(); /// Add lasting effects /// /// \brief addSpell /// \param id ID for stacking purposes. /// \param stack If false, the spell is not added if one with the same ID exists already. /// \param effects /// \param displayName Name for display in magic menu. /// void addSpell (const std::string& id, bool stack, std::vector effects, const std::string& displayName, int casterActorId); /// Removes the active effects from this spell/potion/.. with \a id void removeEffects (const std::string& id); /// Remove all active effects with this effect id void purgeEffect (short effectId); /// Remove all active effects with this effect id and source id void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1); /// Remove all active effects, if roll succeeds (for each effect) void purgeAll(float chance, bool spellOnly = false); /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId void purge (int casterActorId); /// Remove all spells void clear(); bool isSpellActive (const std::string& id) const; ///< case insensitive void purgeCorprusDisease(); const MagicEffects& getMagicEffects() const; void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/actor.cpp000066400000000000000000000022711413061077700230040ustar00rootroot00000000000000#include "actor.hpp" #include "character.hpp" namespace MWMechanics { Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation) { mCharacterController.reset(new CharacterController(ptr, animation)); } void Actor::updatePtr(const MWWorld::Ptr &newPtr) { mCharacterController->updatePtr(newPtr); } CharacterController* Actor::getCharacterController() { return mCharacterController.get(); } int Actor::getGreetingTimer() const { return mGreetingTimer; } void Actor::setGreetingTimer(int timer) { mGreetingTimer = timer; } float Actor::getAngleToPlayer() const { return mTargetAngleRadians; } void Actor::setAngleToPlayer(float angle) { mTargetAngleRadians = angle; } GreetingState Actor::getGreetingState() const { return mGreetingState; } void Actor::setGreetingState(GreetingState state) { mGreetingState = state; } bool Actor::isTurningToPlayer() const { return mIsTurningToPlayer; } void Actor::setTurningToPlayer(bool turning) { mIsTurningToPlayer = turning; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/actor.hpp000066400000000000000000000030341413061077700230070ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_ACTOR_H #define OPENMW_MECHANICS_ACTOR_H #include #include "../mwmechanics/actorutil.hpp" #include namespace MWRender { class Animation; } namespace MWWorld { class Ptr; } namespace MWMechanics { class CharacterController; /// @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); /// Notify this actor of its new base object Ptr, use when the object changed cells void updatePtr(const MWWorld::Ptr& newPtr); CharacterController* getCharacterController(); int getGreetingTimer() const; void setGreetingTimer(int timer); float getAngleToPlayer() const; void setAngleToPlayer(float angle); GreetingState getGreetingState() const; void setGreetingState(GreetingState state); bool isTurningToPlayer() const; void setTurningToPlayer(bool turning); Misc::TimerStatus updateEngageCombatTimer(float duration) { return mEngageCombat.update(duration); } private: std::unique_ptr 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)}; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/actors.cpp000066400000000000000000003634471413061077700232060ustar00rootroot00000000000000#include "actors.hpp" #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 "../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" #include "tickableeffects.hpp" namespace { bool isConscious(const MWWorld::Ptr& ptr) { const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); return !stats.isDead() && !stats.getKnockedDown(); } int getBoundItemSlot (const std::string& itemId) { static std::map boundItemsMap; if (boundItemsMap.empty()) { std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } int slot = MWWorld::InventoryStore::Slot_CarriedRight; std::map::iterator it = boundItemsMap.find(itemId); if (it != boundItemsMap.end()) slot = it->second; return slot; } class CheckActorCommanded : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mActor; public: bool mCommanded; CheckActorCommanded(const MWWorld::Ptr& actor) : mActor(actor) , mCommanded(false){} void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override { if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) mCommanded = true; } }; // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { CheckActorCommanded check(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); stats.getActiveSpells().visitEffectSources(check); bool hasCommandPackage = false; auto it = stats.getAiSequence().begin(); for (; it != stats.getAiSequence().end(); ++it) { if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(it->get())->isCommanded()) { hasCommandPackage = true; break; } } if (!check.mCommanded && hasCommandPackage) stats.getAiSequence().erase(it); } void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); health = 0.1f * endurance; float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); } template void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func) { for(auto& iter : actors) { const MWWorld::Ptr &iteratedActor = iter.first; if (iteratedActor == player || iteratedActor == actor) 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(iter, package)) break; } } } } namespace MWMechanics { static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement static const 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; } } class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor { public: float mRemainingTime; GetStuntedMagickaDuration(const MWWorld::Ptr& actor) : mRemainingTime(0.f){} void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override { if (mRemainingTime == -1) return; if (key.mId == ESM::MagicEffect::StuntedMagicka) { if (totalTime == -1) { mRemainingTime = -1; return; } if (remainingTime > mRemainingTime) mRemainingTime = remainingTime; } } }; class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor { std::string mSpellId; public: GetCurrentMagnitudes(const std::string& spellId) : mSpellId(spellId) { } void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override { if (magnitude <= 0) return; if (sourceId != mSpellId) return; mMagnitudes.emplace_back(key, magnitude); } std::vector> mMagnitudes; }; class GetCorprusSpells : public MWMechanics::EffectSourceVisitor { public: void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override { if (key.mId != ESM::MagicEffect::Corprus) return; mSpells.push_back(sourceId); } std::vector mSpells; }; class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; MWWorld::Ptr mActor; bool mTrapped; public: SoulTrap(const MWWorld::Ptr& trappedCreature) : mCreature(trappedCreature) , mTrapped(false) { } void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override { if (mTrapped) return; if (key.mId != ESM::MagicEffect::Soultrap) return; if (magnitude <= 0) return; MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); if (caster.isEmpty() || !caster.getClass().isActor()) return; static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); int creatureSoulValue = mCreature.get()->mBase->mData.mSoul; if (creatureSoulValue == 0) return; // 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()) return; // Set the soul on just one of the gems, not the whole stack gem->getContainerStore()->unstack(*gem, caster); gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); // Restack the gem with other gems with the same soul gem->getContainerStore()->restack(*gem); mTrapped = true; if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Soul_Trap"); if (fx) MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, "", mCreature.getRefData().getPosition().asVec3()); MWBase::Environment::get().getSoundManager()->playSound3D( mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f ); } }; void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); int slot = getBoundItemSlot(itemId); if (actor.getClass().getContainerStore(actor).count(itemId) != 0) return; MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); MWWorld::ActionEquip action(boundPtr); action.execute(actor); if (actor != MWMechanics::getPlayer()) return; MWWorld::Ptr newItem; auto it = store.getSlot(slot); // 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 Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); int slot = getBoundItemSlot(itemId); MWWorld::ContainerStoreIterator 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; std::string type = currentItem->getTypeName(); if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) 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()) { // Find previous item (or its replacement) by id. // we should equip previous item only if expired bound item was equipped. MWWorld::Ptr item = store.findReplacement(prevItemId); if (!item.isEmpty() && wasEquipped) { MWWorld::ActionEquip action(item); action.execute(actor); } } store.remove(itemId, 1, actor); } void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects adjustMagicEffects (ptr); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); calculateCreatureStatModifiers (ptr, duration); // fatigue restoration calculateRestoration(ptr, duration); } void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, bool inCombatOrPursue) { if (!actor.getRefData().getBaseNode()) return; if (targetActor.getClass().getCreatureStats(targetActor).isDead()) 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(actor.getRefData().getPosition().asVec3()); const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); 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 = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); osg::Vec3f targetDirection(actor2Pos - actor1Pos); actorDirection.z() = 0; targetDirection.z() = 0; if ((actorDirection * targetDirection > 0 || inCombatOrPursue) && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) { sqrHeadTrackDistance = sqrDist; headTrackTarget = targetActor; } } void Actors::playIdleDialogue(const MWWorld::Ptr& actor) { if (!actor.getClass().isActor() || actor == getPlayer() || MWBase::Environment::get().getSoundManager()->sayActive(actor)) return; const CreatureStats &stats = actor.getClass().getCreatureStats(actor); if (stats.getAiSetting(CreatureStats::AI_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* 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() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) { if (mSmoothMovement) return; CreatureStats &stats = actor.getClass().getCreatureStats(actor); MWMechanics::AiSequence& seq = stats.getAiSequence(); if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed()) { osg::Vec3f targetPos = seq.getActivePackage().getDestination(); osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); float distance = (targetPos - actorPos).length(); if (distance < DECELERATE_DISTANCE) { float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE); auto& movement = actor.getClass().getMovementSettings(actor); movement.mPosition[0] *= speedCoef; movement.mPosition[1] *= speedCoef; } } } void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) { if (!actor.getClass().isActor() || actor == getPlayer()) return; CreatureStats &stats = actor.getClass().getCreatureStats(actor); const MWMechanics::AiSequence& seq = stats.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; } MWWorld::Ptr player = getPlayer(); osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); 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 int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); float helloDistance = static_cast(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); int greetingTimer = actorState.getGreetingTimer(); GreetingState greetingState = actorState.getGreetingState(); if (greetingState == Greet_None) { if ((playerPos - actorPos).length2() <= helloDistance*helloDistance && !player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed() && 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 (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.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) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).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::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // 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()); 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); // 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())) { MWBase::Environment::get().getMechanicsManager()->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; getActorsSidingWith(MWMechanics::getPlayer(), 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 (actor2.getClass().getCreatureStats(actor2).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)) { MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) MWBase::Environment::get().getMechanicsManager()->startCombat(ally1, actor2); 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 (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1)) aggressive = true; else { for (const MWWorld::Ptr &ally : allies1) { if (actor2.getClass().getCreatureStats(actor2).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 = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far static const float fAlarmRadius = MWBase::Environment::get().getWorld()->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 = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); if (LOS) MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); } } void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); if (creatureStats.isDeathAnimationFinished()) return; MagicEffects now = creatureStats.getSpells().getMagicEffects(); if (creature.getClass().hasInventoryStore(creature)) { MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); now += store.getMagicEffects(); } now += creatureStats.getActiveSpells().getMagicEffects(); creatureStats.modifyMagicEffects(now); } void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); float base = 1.f; if (ptr == getPlayer()) base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); else base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); double magickaFactor = base + creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; DynamicStat magicka = creatureStats.getMagicka(); float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); float currentToBaseRatio = (magicka.getCurrent() / magicka.getBase()); magicka.setModified(magicka.getModified() + diff, 0); magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); creatureStats.setMagicka(magicka); } void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); if (stats.isDead()) return; const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); if (sleep) { float health, magicka; getRestorationPerHourOfSleep(ptr, health, magicka); DynamicStat stat = stats.getHealth(); stat.setCurrent(stat.getCurrent() + health * hours); stats.setHealth(stat); double restoreHours = hours; bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; if (stunted) { // Stunted Magicka effect should be taken into account. GetStuntedMagickaDuration visitor(ptr); stats.getActiveSpells().visitEffectSources(visitor); stats.getSpells().visitEffectSources(visitor); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor); // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. if (visitor.mRemainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if(timeScale == 0.0) timeScale = 1; restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); } else if (visitor.mRemainingTime == -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 float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); if (normalizedEncumbrance > 1) normalizedEncumbrance = 1; float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); x *= fEndFatigueMult * endurance; fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours); stats.setFatigue (fatigue); } void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) { 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 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 (); float x = fFatigueReturnBase + fFatigueReturnMult * endurance; fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); } class ExpiryVisitor : public EffectSourceVisitor { private: MWWorld::Ptr mActor; float mDuration; public: ExpiryVisitor(const MWWorld::Ptr& actor, float duration) : mActor(actor), mDuration(duration) { } void visit (MWMechanics::EffectKey key, int /*effectIndex*/, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float magnitude, float remainingTime = -1, float /*totalTime*/ = -1) override { if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration) { CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); if (effectTick(creatureStats, mActor, key, magnitude * remainingTime)) creatureStats.getMagicEffects().add(key, -magnitude); } } }; void Actors::applyCureEffects(const MWWorld::Ptr& actor) { CreatureStats &creatureStats = actor.getClass().getCreatureStats(actor); const MagicEffects &effects = creatureStats.getMagicEffects(); if (effects.get(ESM::MagicEffect::CurePoison).getModifier() > 0) { creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Poison); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); } if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) { creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); } if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) { creatureStats.getSpells().purgeCommonDisease(); } if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) { creatureStats.getSpells().purgeBlightDisease(); } if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) { creatureStats.getActiveSpells().purgeCorprusDisease(); creatureStats.getSpells().purgeCorprusDisease(); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); } if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) { creatureStats.getSpells().purgeCurses(); } } void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); applyCureEffects(ptr); bool wasDead = creatureStats.isDead(); if (duration > 0) { // Apply correct magnitude for tickable effects that have just expired, // in case duration > remaining time of effect. // One case where this will happen is when the player uses the rest/wait command // while there is a tickable effect active that should expire before the end of the rest/wait. ExpiryVisitor visitor(ptr, duration); creatureStats.getActiveSpells().visitEffectSources(visitor); for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { // tickable effects (i.e. effects having a lasting impact after expiry) effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities if (it->second.getMagnitude() > 0) { CastSpell cast(ptr, ptr); if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) { creatureStats.getSpells().purgeEffect(it->first.mId); creatureStats.getActiveSpells().purgeEffect(it->first.mId); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); } } } } // purge levitate effect if levitation is disabled // check only modifier, because base value can be setted from SetFlying console command. if (MWBase::Environment::get().getWorld()->isLevitationEnabled() == false && effects.get(ESM::MagicEffect::Levitate).getModifier() > 0) { creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Levitate); creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Levitate); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).purgeEffect(ESM::MagicEffect::Levitate); if (ptr == getPlayer()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); } } // dynamic stats for (int i = 0; i < 3; ++i) { DynamicStat stat = creatureStats.getDynamic(i); float fortify = effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude(); float drain = 0.f; if (!godmode) drain = effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(); stat.setCurrentModifier(fortify - drain, // Magicka can be decreased below zero due to a fortify effect wearing off // Fatigue can be decreased below zero meaning the actor will be knocked out i == 1 || i == 2); creatureStats.setDynamic(i, stat); } // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { AttributeValue stat = creatureStats.getAttribute(i); float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude(); float drain = 0.f, absorb = 0.f; if (!godmode) { drain = effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude(); absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude(); } stat.setModifier(static_cast(fortify - drain - absorb)); creatureStats.setAttribute(i, stat); } if (creatureStats.needToRecalcDynamicStats()) calculateDynamicStats(ptr); if (ptr == getPlayer()) { GetCorprusSpells getCorprusSpellsVisitor; creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor); creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor); ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor); std::vector corprusSpells = getCorprusSpellsVisitor.mSpells; std::vector corprusSpellsToRemove; for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it) { if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end()) { // Corprus effect expired, remove entry and restore stats. MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first); corprusSpellsToRemove.push_back(it->first); corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); continue; } corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening) { it->second.mNextWorsening += CorprusStats::sWorseningPeriod; GetCurrentMagnitudes getMagnitudesVisitor (it->first); creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor); creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor); ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor); for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes) { if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute) { AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); attr.damage(-effectMagnitude.second); creatureStats.setAttribute(effectMagnitude.first.mArg, attr); it->second.mWorsenings[effectMagnitude.first.mArg] = 0; } else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute) { AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); int currentDamage = attr.getDamage(); if (currentDamage >= 0) it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage); it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second; attr.damage(effectMagnitude.second); creatureStats.setAttribute(effectMagnitude.first.mArg, attr); } } MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); } } for (std::string& oldCorprusSpell : corprusSpellsToRemove) { creatureStats.removeCorprusSpell(oldCorprusSpell); } for (std::string& newCorprusSpell : corprusSpells) { CorprusStats corprus; for (int i=0; igetTimeStamp() + CorprusStats::sWorseningPeriod; creatureStats.addCorprusSpell(newCorprusSpell, corprus); } } // AI setting modifiers int creature = !ptr.getClass().isNpc(); if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Humanoid) creature = false; // Note: the Creature variants only work on normal creatures, not on daedra or undead creatures. if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); stat.setModifier(static_cast(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); stat.setModifier(static_cast(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } 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(); MWWorld::Ptr player = getPlayer(); std::set playerFollowers; getActorsSidingWith(player, playerFollowers); for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { bool actorKilled = false; const ActiveSpells::ActiveSpellParams& spell = it->second; MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); for (std::vector::const_iterator effectIt = spell.mEffects.begin(); effectIt != spell.mEffects.end(); ++effectIt) { int effectId = effectIt->mEffectId; bool isDamageEffect = false; int damageEffects[] = { ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth }; for (unsigned int i=0; iactorKilled(ptr, player); actorKilled = true; break; } } } if (actorKilled) break; } } // TODO: dirty flag for magic effects to avoid some unnecessary work below? // any value of calm > 0 will stop the actor from fighting if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) creatureStats.getAiSequence().stopCombat(); // Update bound effects // Note: in vanilla MW multiple bound items of the same type can be created by different spells. // As these extra copies are kinda useless this may or may not be important. static std::map boundItemsMap; if (boundItemsMap.empty()) { boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID"; boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID"; boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID"; boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below) boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID"; boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID"; boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID"; boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID"; boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID"; boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; } if(ptr.getClass().hasInventoryStore(ptr)) { for (const auto& [effect, itemGmst] : boundItemsMap) { bool found = creatureStats.mBoundItems.find(effect) != creatureStats.mBoundItems.end(); float magnitude = effects.get(effect).getMagnitude(); if (found != (magnitude > 0)) { if (magnitude > 0) creatureStats.mBoundItems.insert(effect); else creatureStats.mBoundItems.erase(effect); std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->mValue.getString(); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); if (effect == ESM::MagicEffect::BoundGloves) { item = MWBase::Environment::get().getWorld()->getStore().get().find( "sMagicBoundRightGauntletID")->mValue.getString(); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } } } } // Summoned creature update visitor assumes the actor belongs to a cell. // This assumption isn't always valid for the player character. if (!ptr.isInCell()) return; bool hasSummonEffect = false; for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { if (isSummoningEffect(it->first.mId)) { hasSummonEffect = true; break; } } if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect) { UpdateSummonedCreatures updateSummonedCreatures(ptr); creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); creatureStats.getSpells().visitEffectSources(updateSummonedCreatures); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f); } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) { NpcStats &npcStats = ptr.getClass().getNpcStats(ptr); const MagicEffects &effects = npcStats.getMagicEffects(); bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); // skills for(int i = 0;i < ESM::Skill::Length;++i) { SkillValue& skill = npcStats.getSkill(i); float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude(); float drain = 0.f, absorb = 0.f; if (!godmode) { drain = effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude(); absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude(); } skill.setModifier(static_cast(fortify - drain - absorb)); } } bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); return ctrl->isAttackPreparing(); } bool Actors::isRunning(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); return ctrl->isRunning(); } bool Actors::isSneaking(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); return ctrl->isSneaking(); } void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) { NpcStats &stats = ptr.getClass().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 = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if (seq.getTypeId() != AiPackageTypeId::Breathe) //Only add it once seq.stack(AiBreathe(), ptr); } MWBase::World *world = MWBase::Environment::get().getWorld(); 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); } bool godmode = isPlayer && MWBase::Environment::get().getWorld()->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); } void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip) { bool isPlayer = (ptr == getPlayer()); MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); /** * Automatically equip NPCs torches at night and unequip them at day */ if (!isPlayer) { MWWorld::ContainerStoreIterator torch = inventoryStore.end(); for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) { if (it->getTypeName() == typeid(ESM::Light).name() && it->getClass().canBeEquipped(*it, ptr).first) { torch = it; break; } } if (mayEquip) { if (torch != inventoryStore.end()) { if (!ptr.getClass().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->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); } else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) { // 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, torch, ptr); } } } else { if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name()) { // 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... if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) { // Use time from the player's light if(isPlayer) { // But avoid using it up if the light source is hidden MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (anim && anim->getCarriedLeftShown()) { 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(MWBase::Environment::get().getWorld()->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) { MWWorld::Ptr player = getPlayer(); if (ptr != player && ptr.getClass().isNpc()) { // get stats of witness CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); if (player.getClass().getNpcStats(player).isWerewolf()) return; if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackageTypeId::Pursue && !creatureStats.getAiSequence().isInCombat() && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->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 ( player.getClass().getNpcStats(player).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? && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) { MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); creatureStats.setHitAttemptActorId(player.getClass().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(MWBase::Environment::get().getWorld()->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() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPursuit(); creatureStats.getAiSequence().stopCombat(); // Reset factors to attack creatureStats.setAttacked(false); creatureStats.setAlarmed(false); creatureStats.setAiSetting(CreatureStats::AI_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(); } Actors::~Actors() { clear(); } 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 const float maxProcessingRange = 7168.f; static const float minProcessingRange = maxProcessingRange / 2.f; float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game"); actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange); actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange); mActorsProcessingRange = actorsProcessingRange; } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) ctrl->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, ctrl); } void Actors::updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl) { 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; float fadeStartDistance = mActorsProcessingRange*0.9f; float fadeEndDistance = mActorsProcessingRange; 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) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { delete iter->second; mActors.erase(iter); } } void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) iter->second->getCharacterController()->castSpell(spellId, manualSpell); } bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) { 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; 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) { PtrActorMap::iterator iter = mActors.find(old); if(iter != mActors.end()) { Actor *actor = iter->second; mActors.erase(iter); actor->updatePtr(ptr); mActors.insert(std::make_pair(ptr, actor)); } } void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) { PtrActorMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) { delete iter->second; mActors.erase(iter++); } else ++iter; } } void Actors::updateCombatMusic () { MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); bool hasHostiles = false; // need to know this to play Battle music bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); if (aiActive) { for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first == player) continue; bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange; if (inProcessingRange) { MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); if (!stats.isDead() && stats.getAiSequence().isInCombat()) { hasHostiles = true; break; } } } } // check if we still have any player enemies to switch music static int currentMusic = 0; if (currentMusic != 1 && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); currentMusic = 1; } else if (currentMusic != 2 && hasHostiles) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); currentMusic = 2; } } void Actors::predictAndAvoidCollisions(float duration) { 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"); MWWorld::Ptr player = getPlayer(); MWBase::World* world = MWBase::Environment::get().getWorld(); for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Ptr& ptr = iter->first; if (ptr == player) continue; // Don't interfere with player controls. 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); osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); 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(); for (const auto& package : aiSequence) { if (package->getTypeId() == AiPackageTypeId::Follow) shouldAvoidCollision = true; else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package.get())->isStationary()) shouldGiveWay = true; } else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) { currentTarget = package->getTarget(); shouldAvoidCollision = isMoving; shouldTurnToApproachingActor = false; break; } } if (!shouldAvoidCollision && !shouldGiveWay) continue; osg::Vec2f baseSpeed = origMovement * maxSpeed; osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); float baseRotZ = ptr.getRefData().getPosition().rot[2]; osg::Vec3f halfExtents = world->getHalfExtents(ptr); 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(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter) { const MWWorld::Ptr& otherPtr = otherIter->first; if (otherPtr == ptr || otherPtr == currentTarget) continue; osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); 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; osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() * otherPtr.getClass().getMaxSpeed(otherPtr); float rotZ = otherPtr.getRefData().getPosition().rot[2]; osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x(); collisionDist = std::min(collisionDist, relPos.length()); // Find the earliest `t` when |relPos + relSpeed * t| == collisionDist. float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y(); float v2 = relSpeed.length2(); float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist); if (Dh <= 0 || v2 == 0) continue; // No solution; distance is always >= collisionDist. 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()); osg::Vec2f posAtT = relPos + relSpeed * t; float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed); coef *= osg::clampBetween((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) { static float timerUpdateHeadTrack = 0; static float timerUpdateEquippedLight = 0; static float timerUpdateHello = 0; const float updateEquippedLightInterval = 1.0f; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations MWBase::World* world = MWBase::Environment::get().getWorld(); bool showTorches = world->useTorches(); 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 bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); 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); } bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { bool isPlayer = iter->first == player; CharacterController* ctrl = iter->second->getCharacterController(); float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within given distance to the player. bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; if (isPlayer) ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell()); // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead() || !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat() || !inProcessingRange)) { iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(-1); if (player.getClass().getCreatureStats(player).getHitAttemptActorId() == iter->first.getClass().getCreatureStats(iter->first).getActorId()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); // For dead actors we need to update looping spell particles if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { // They can be added during the death animation if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()) adjustMagicEffects(iter->first); ctrl->updateContinuousVfx(); } else { bool cellChanged = world->hasCellChanged(); MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports updateActor(actor, 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(iter->first); for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first || isPlayer) // player is not AI-controlled continue; engageCombat(iter->first, it->first, cachedAllies, it->first == player); } } if (timerUpdateHeadTrack == 0) { float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); bool firstPersonPlayer = isPlayer && world->isFirstPerson(); bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue); MWWorld::Ptr activePackageTarget; // 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) { if (inCombatOrPursue) activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); for (PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first) continue; if (inCombatOrPursue && it->first != activePackageTarget) continue; updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); } } ctrl->setHeadTrackTarget(headTrackTarget); } if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePursuit(iter->first, duration); if (iter->first != player) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) { stats.getAiSequence().execute(iter->first, *ctrl, duration); updateGreetingState(iter->first, *iter->second, timerUpdateHello > 0); playIdleDialogue(iter->first); updateMovementSpeed(iter->first); } } } else if (aiActive && iter->first != player && isConscious(iter->first)) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true); } if(iter->first.getClass().isNpc()) { // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe if (inProcessingRange) updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); calculateNpcStatModifiers(iter->first, duration); if (timerUpdateEquippedLight == 0) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } } } static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game"); if (avoidCollisions) predictAndAvoidCollisions(duration); timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; timerUpdateHello += duration; mTimerDisposeSummonsCorpses += duration; // Animation/movement update CharacterController* playerCharacter = nullptr; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length(); bool isPlayer = iter->first == player; CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); // Actors with active AI should be able to move. bool alwaysActive = false; if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed()) { MWMechanics::AiSequence& seq = stats.getAiSequence(); alwaysActive = !seq.isEmpty() && seq.getActivePackage().alwaysActive(); } bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive; int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) if (isPlayer) activeFlag = 2; int active = inRange ? activeFlag : 0; CharacterController* ctrl = iter->second->getCharacterController(); ctrl->setActive(active); if (!inRange) { iter->first.getRefData().getBaseNode()->setNodeMask(0); world->setActorCollisionMode(iter->first, false, false); continue; } else if (!isPlayer) iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); if (!isDead && (!godmode || !isPlayer) && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) ctrl->skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) if (iter->first == getPlayer()) { playerCharacter = ctrl; continue; } world->setActorCollisionMode(iter->first, true, !iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()); ctrl->update(duration); updateVisibility(iter->first, ctrl); } if (playerCharacter) { playerCharacter->update(duration); playerCharacter->setVisibility(1.f); } for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); //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) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { if(iter->second->getCharacterController()->isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); iter->second->getCharacterController()->resurrect(); } } } void Actors::killDeadActors() { for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); if(!stats.isDead()) continue; MWBase::Environment::get().getWorld()->removeActorPath(iter->first); CharacterController::KillResult killResult = iter->second->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(iter->first, "hit"); // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) { SoulTrap soulTrap (iter->first); stats.getActiveSpells().visitEffectSources(soulTrap); } // Magic effects will be reset later, and the magic effect that could kill the actor // needs to be determined now calculateCreatureStatModifiers(iter->first, 0); if (cls.isEssential(iter->first)) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } else if (killResult == CharacterController::Result_DeathAnimJustFinished) { bool isPlayer = iter->first == getPlayer(); notifyDied(iter->first); // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.getActiveSpells().clear(); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); // Reset dynamic stats, attributes and skills calculateCreatureStatModifiers(iter->first, 0); if (iter->first.getClass().isNpc()) calculateNpcStatModifiers(iter->first, 0); if (isPlayer) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); } else { // NPC death animation is over, disable actor collision MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); } // Play Death Music if it was the player dying if(iter->first == getPlayer()) MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } } } void Actors::cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) { 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) MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, "", ptr.getRefData().getPosition().asVec3()); // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); std::map& 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) { for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells(); spells.purge(casterActorId); } } void Actors::rest(double hours, bool sleep) { float duration = hours * 3600.f; 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(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); continue; } if (!sleep || iter->first == player) restoreDynamicStats(iter->first, hours, sleep); if ((!iter->first.getRefData().getBaseNode()) || (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; adjustMagicEffects (iter->first); if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) calculateDynamicStats (iter->first); calculateCreatureStatModifiers (iter->first, duration); if (iter->first.getClass().isNpc()) calculateNpcStatModifiers(iter->first, duration); iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) { animation->removeEffects(); MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first); } } fastForwardAi(); } void Actors::updateSneaking(CharacterController* ctrl, float duration) { static float sneakTimer = 0.f; // Times update of sneak icon if (!ctrl) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; } MWWorld::Ptr player = getPlayer(); if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player)) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; } static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" MWBase::World* 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 (sneakTimer >= fSneakUseDelay) sneakTimer = 0.f; if (sneakTimer == 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; osg::Vec3f position(player.getRefData().getPosition().asVec3()); float radius = std::min(fSneakUseDist, mActorsProcessingRange); getObjectsInRange(position, radius, observers); for (const MWWorld::Ptr &observer : observers) { if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) 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 (sneakSkillTimer >= fSneakUseDelay) sneakSkillTimer = 0.f; if (avoidedNotice && sneakSkillTimer == 0.f) player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); } sneakTimer += duration; sneakSkillTimer += duration; } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const { float healthPerHour, magickaPerHour; getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; float healthHours = healthPerHour > 0 ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour : 1.0f; float magickaHours = magickaPerHour > 0 && !stunted ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour : 1.0f; int autoHours = static_cast(std::ceil(std::max(1.f, std::max(healthHours, magickaHours)))); return autoHours; } int Actors::countDeaths (const std::string& id) const { std::map::const_iterator iter = mDeathCount.find(id); if(iter != mDeathCount.end()) return iter->second; return 0; } void Actors::forceStateUpdate(const MWWorld::Ptr & ptr) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) iter->second->getCharacterController()->forceStateUpdate(); } bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.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) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) iter->second->getCharacterController()->skipAnim(); } bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) return iter->second->getCharacterController()->isAnimPlaying(groupName); return false; } void Actors::persistAnimationStates() { for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) iter->second->getCharacterController()->persistAnimationState(); } void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) out.push_back(iter->first); } } bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) { for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) return true; } return false; } std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { std::list list; for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { const MWWorld::Ptr &iteratedActor = iter->first; if (iteratedActor == getPlayer()) continue; const bool sameActor = (iteratedActor == actor); 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 Combat and 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 (package->sideWithTarget() && !package->getTarget().isEmpty()) { if (sameActor) { list.push_back(package->getTarget()); } else if (package->getTarget() == actor) { list.push_back(iteratedActor); } break; } else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) break; } } return list; } std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) list.push_back(iter.first); 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) { std::list 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) { std::list followers = getActorsSidingWith(actor); for(const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsSidingWith(follower, out); } void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map >& cachedAllies) { // 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 { std::list followers = getActorsSidingWith(actor); for (const MWWorld::Ptr &follower : followers) 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::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { std::list list; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_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) { std::map map; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { int index = static_cast(package.get())->getFollowIndex(); map[index] = iter.first; return false; } else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) return false; return true; }); return map; } std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; 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 CreatureStats &stats = neighbor.getClass().getCreatureStats(neighbor); if (stats.isDead()) continue; if (stats.getAiSequence().isInCombat(actor)) list.push_front(neighbor); } return list; } std::list Actors::getEnemiesNearby(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { 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 (std::map::const_iterator it = mDeathCount.begin(); it != mDeathCount.end(); ++it) { writer.writeHNString("ID__", it->first); writer.writeHNT ("COUN", it->second); } 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() { PtrActorMap::iterator it(mActors.begin()); for (; it != mActors.end(); ++it) { delete it->second; it->second = nullptr; } mActors.clear(); mDeathCount.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) { adjustMagicEffects(ptr); calculateCreatureStatModifiers(ptr, 0.f); if (ptr.getClass().isNpc()) calculateNpcStatModifiers(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return false; return it->second->getCharacterController()->isReadyToBlock(); } bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return false; return it->second->getCharacterController()->isCastingSpell(); } bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); return ctrl->isAttackingOrSpell(); } int Actors::getGreetingTimer(const MWWorld::Ptr& ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return 0; return it->second->getGreetingTimer(); } float Actors::getAngleToPlayer(const MWWorld::Ptr& ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return 0.f; return it->second->getAngleToPlayer(); } GreetingState Actors::getGreetingState(const MWWorld::Ptr& ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return Greet_None; return it->second->getGreetingState(); } bool Actors::isTurningToPlayer(const MWWorld::Ptr& ptr) const { PtrActorMap::const_iterator it = mActors.find(ptr); if (it == mActors.end()) return false; return it->second->isTurningToPlayer(); } void Actors::fastForwardAi() { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; // making a copy since fast-forward could move actor to a different cell and invalidate the mActors iterator PtrActorMap map = mActors; for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it) { MWWorld::Ptr ptr = it->first; 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.47.0/apps/openmw/mwmechanics/actors.hpp000066400000000000000000000217601413061077700232000ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTORS_H #define GAME_MWMECHANICS_ACTORS_H #include #include #include #include #include #include "../mwmechanics/actorutil.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 { std::map mDeathCount; void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void adjustMagicEffects (const MWWorld::Ptr& creature); void calculateDynamicStats (const MWWorld::Ptr& ptr); void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); void killDeadActors (); void purgeSpellEffects (int casterActorId); void predictAndAvoidCollisions(float duration); public: Actors(); ~Actors(); typedef std::map PtrActorMap; PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { 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); /// 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); 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); ///< Deregister an actor for stats management /// /// \note Ignored, if \a ptr is not a registered actor. void resurrect (const MWWorld::Ptr& ptr); void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false); void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); ///< 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); ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. /** 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); void playIdleDialogue(const MWWorld::Ptr& actor); void updateMovementSpeed(const MWWorld::Ptr& actor); void updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly); void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir); void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, bool inCombatOrPursue); void rest(double hours, bool sleep); ///< 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); 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(); ///< 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); bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); void forceStateUpdate(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); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); bool isAnyObjectInRange(const osg::Vec3f& position, float radius); void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); ///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::list getActorsSidingWith(const MWWorld::Ptr& actor); std::list getActorsFollowing(const MWWorld::Ptr& actor); /// Recursive version of getActorsFollowing void getActorsFollowing(const MWWorld::Ptr &actor, std::set& out); /// Recursive version of getActorsSidingWith void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out); /// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of actors mapped to their allies void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map >& cachedAllies); /// Get the list of AiFollow::mFollowIndex for all actors following this target std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor); ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); /// Unlike getActorsFighting, also returns actors that *would* fight the given actor if they saw him. std::list getEnemiesNearby(const MWWorld::Ptr& actor); 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: void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); void applyCureEffects (const MWWorld::Ptr& actor); PtrActorMap mActors; float mTimerDisposeSummonsCorpses; float mActorsProcessingRange; bool mSmoothMovement; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/actorutil.cpp000066400000000000000000000016251413061077700237040ustar00rootroot00000000000000#include "actorutil.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" 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; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/actorutil.hpp000066400000000000000000000060761413061077700237160ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "./creaturestats.hpp" namespace MWWorld { class Ptr; } namespace MWMechanics { enum GreetingState { Greet_None, Greet_InProgress, Greet_Done }; MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); bool hasWaterWalking(const MWWorld::Ptr& actor); template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) { T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); switch(setting) { case MWMechanics::CreatureStats::AiSetting::AI_Hello: copy.mAiData.mHello = value; break; case MWMechanics::CreatureStats::AiSetting::AI_Fight: copy.mAiData.mFight = value; break; case MWMechanics::CreatureStats::AiSetting::AI_Flee: copy.mAiData.mFlee = value; break; case MWMechanics::CreatureStats::AiSetting::AI_Alarm: copy.mAiData.mAlarm = value; break; default: assert(0); } MWBase::Environment::get().getWorld()->createOverrideRecord(copy); } 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)) { 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); } } template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/aiactivate.cpp000066400000000000000000000045301413061077700240060ustar00rootroot00000000000000#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(const std::string &objectId) : 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 { std::unique_ptr activate(new ESM::AiSequence::AiActivate()); activate->mTargetId = mObjectId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Activate; package.mPackage = activate.release(); sequence.mPackages.push_back(package); } AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) : mObjectId(activate->mTargetId) { } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/aiactivate.hpp000066400000000000000000000022041413061077700240070ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H #include "typedaipackage.hpp" #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(const std::string &objectId); 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.47.0/apps/openmw/mwmechanics/aiavoiddoor.cpp000066400000000000000000000053561413061077700242030ustar00rootroot00000000000000#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() { mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS); } float MWMechanics::AiAvoidDoor::getAdjustedAngle() const { return 2 * osg::PI / MAX_DIRECTIONS * mDirection; } openmw-openmw-0.47.0/apps/openmw/mwmechanics/aiavoiddoor.hpp000066400000000000000000000027101413061077700241770ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aibreathe.cpp000066400000000000000000000017761413061077700236310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aibreathe.hpp000066400000000000000000000015641413061077700236310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aicast.cpp000066400000000000000000000057041413061077700231440ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aicast.hpp000066400000000000000000000022511413061077700231430ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aicombat.cpp000066400000000000000000000763661413061077700234730ustar00rootroot00000000000000#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 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 false; 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); if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(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(); characterController.setAttackingOrSpell(false); storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted const std::list& 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::shared_ptr& currentAction = storage.mCurrentAction; if (!forceFlee) { if (actionCooldown > 0) return false; if (characterController.readyToPrepareAttack()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } } else { currentAction.reset(new ActionFlee()); 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 halfExtents = world->getPathfindingHalfExtents(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, halfExtents, navigatorFlags, areaCosts); 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 = navigator->raycast(halfExtents, 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, halfExtents, navigatorFlags, areaCosts); 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(); characterController.setAttackingOrSpell(false); currentAction.reset(new ActionFlee()); 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()) { ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())]; 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]; smoothTurn(actor, targetAngleRadians, axis, eps); } MWWorld::Ptr AiCombat::getTarget() const { return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr combat(new ESM::AiSequence::AiCombat()); combat->mTargetActorId = mTargetActorId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Combat; package.mPackage = combat.release(); sequence.mPackages.push_back(package); } void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { // 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(); 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() < 0.25) moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); if (moveDuration > 0) { mMovement.mPosition[0] = Misc::Rng::rollProbability() < 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 characterController.setAttackingOrSpell(true); if (!distantCombat) characterController.setAIAttackType(chooseBestAttack(weapon)); mStrength = Misc::Rng::rollClosedProbability(); 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() < iVoiceAttackOdds) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } else mAttackCooldown -= AI_REACTION_TIME; } } void AiCombatStorage::updateAttack(CharacterController& characterController) { if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) { mAttack = false; } characterController.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 chooseBestAttack(const ESM::Weapon* weapon) { std::string attackType; 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; float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); if(roll <= slash) attackType = "slash"; else if(roll <= (slash + thrust)) attackType = "thrust"; else attackType = "chop"; } else MWMechanics::CharacterController::setAttackTypeRandomly(attackType); return attackType; } 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.47.0/apps/openmw/mwmechanics/aicombat.hpp000066400000000000000000000107151413061077700234620ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AICOMBAT_H #define GAME_MWMECHANICS_AICOMBAT_H #include "typedaipackage.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; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; std::shared_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(): mAttackCooldown(0.0f), mTimerCombatMove(0.0f), mReadyToAttack(false), mAttack(false), mAttackRange(0.0f), mCombatMove(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 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(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.47.0/apps/openmw/mwmechanics/aicombataction.cpp000066400000000000000000000500661413061077700246560ustar00rootroot00000000000000#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 "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().apply(actor, mPotion.getCellRef().getRefId(), actor); actor.getClass().getContainerStore(actor).remove(mPotion, 1, 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::shared_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::shared_ptr bestAction (new ActionWeapon(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.reset(new ActionPotion(*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.reset(new ActionEnchantedItem(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.reset(new ActionWeapon(*it, ammo)); antiFleeRating = vanillaRateWeaponAndAmmo(*it, ammo, actor, enemy); } } } for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { float rating = rateSpell(it->first, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; bestAction.reset(new ActionSpell(it->first->mId)); antiFleeRating = vanillaRateSpell(it->first, actor, enemy); } } if (makeFleeDecision(actor, enemy, antiFleeRating)) bestAction.reset(new ActionFlee()); 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 (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { float rating = rateSpell(it->first, 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(); int flee = stats.getAiSetting(CreatureStats::AI_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().getModified() == 0.0f) ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); 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.47.0/apps/openmw/mwmechanics/aicombataction.hpp000066400000000000000000000071531413061077700246620ustar00rootroot00000000000000#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::shared_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.47.0/apps/openmw/mwmechanics/aiescort.cpp000066400000000000000000000121451413061077700235060ustar00rootroot00000000000000#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(const std::string &actorId, int duration, float x, float y, float z) : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) : 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 = actorId; } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) // 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. , mDuration(escort->mRemainingDuration > 0) , 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 { std::unique_ptr escort(new ESM::AiSequence::AiEscort()); escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; escort->mTargetId = mTargetActorRefId; escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Escort; package.mPackage = escort.release(); sequence.mPackages.push_back(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.47.0/apps/openmw/mwmechanics/aiescort.hpp000066400000000000000000000042361413061077700235150ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIESCORT_H #define GAME_MWMECHANICS_AIESCORT_H #include "typedaipackage.hpp" #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(const std::string &actorId, int duration, float x, float y, float z); /// 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(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); 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.47.0/apps/openmw/mwmechanics/aiface.cpp000066400000000000000000000010411413061077700230760ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aiface.hpp000066400000000000000000000016641413061077700231160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aifollow.cpp000066400000000000000000000214521413061077700235120ustar00rootroot00000000000000#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(const std::string &actorId, float duration, float x, float y, float z) : mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) : mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) : mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) : mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } 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)) , mAlwaysFollow(follow->mAlwaysFollow) // mDuration isn't saved in the save file, so just giving it "1" for now if the package had 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. , mDuration(follow->mRemainingDuration) , 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 return true; } else { if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to 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 { std::unique_ptr follow(new ESM::AiSequence::AiFollow()); follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; follow->mTargetId = mTargetActorRefId; follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = isCommanded(); follow->mActive = mActive; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; package.mPackage = follow.release(); sequence.mPackages.push_back(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.47.0/apps/openmw/mwmechanics/aifollow.hpp000066400000000000000000000066001413061077700235150ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIFOLLOW_H #define GAME_MWMECHANICS_AIFOLLOW_H #include "typedaipackage.hpp" #include #include #include "../mwworld/ptr.hpp" namespace ESM { namespace 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: AiFollow(const std::string &actorId, float duration, float x, float y, float z); AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z); /// Follow Actor for duration or until you arrive at a world position AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z); /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); /// 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.47.0/apps/openmw/mwmechanics/aipackage.cpp000066400000000000000000000433701413061077700236060ustar00rootroot00000000000000#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); } } MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), mOptions(options), mTargetActorRefId(""), mTargetActorId(-1), mRotateOnTheRunChecks(0), mIsShortcutting(false), mShortcutProhibited(false), mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { if (mTargetActorId == -2) return MWWorld::Ptr(); if (mTargetActorId == -1) { if (mTargetActorRefId.empty()) { mTargetActorId = -2; return MWWorld::Ptr(); } MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); if (target.isEmpty()) { mTargetActorId = -2; return target; } else mTargetActorId = target.getClass().getCreatureStats(target).getActorId(); } if (mTargetActorId != -1) return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); else return MWWorld::Ptr(); } void MWMechanics::AiPackage::reset() { // reset all members mReaction.reset(); mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); mPathFinder.clearPath(); mObstacleCheck.clear(); } bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) { 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 osg::Vec3f halfExtents = world->getHalfExtents(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(), halfExtents, 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 (actor.getClass().isBipedal(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 { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); 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, halfExtents); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ, halfExtents, 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(), halfExtents, 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() && actor.getClass().isBipedal(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 (actorClass.isBipedal(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(); if (flags & DetourNavigator::Flag_swim) costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor)); if (flags & DetourNavigator::Flag_walk) { float walkCost; if (getTypeId() == AiPackageTypeId::Wander) walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor)); else walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor)); costs.mDoor = costs.mDoor * walkCost; costs.mPathgrid = costs.mPathgrid * walkCost; costs.mGround = costs.mGround * walkCost; } 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.47.0/apps/openmw/mwmechanics/aipackage.hpp000066400000000000000000000156521413061077700236150ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H #include #include #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" #include "aipackagetypeid.hpp" #include "aitimer.hpp" namespace MWWorld { class Ptr; } 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. Currently only used for Wander packages. 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); /// 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; 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.47.0/apps/openmw/mwmechanics/aipackagetypeid.hpp000066400000000000000000000012161413061077700250230ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aipursue.cpp000066400000000000000000000055431413061077700235360ustar00rootroot00000000000000#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 "combat.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) && 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 { return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr pursue(new ESM::AiSequence::AiPursue()); pursue->mTargetActorId = mTargetActorId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Pursue; package.mPackage = pursue.release(); sequence.mPackages.push_back(package); } } // namespace MWMechanics openmw-openmw-0.47.0/apps/openmw/mwmechanics/aipursue.hpp000066400000000000000000000026421413061077700235400ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/aisequence.cpp000066400000000000000000000372441413061077700240260ustar00rootroot00000000000000#include "aisequence.hpp" #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); } AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(AiPackageTypeId::None) {} AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; mRepeat = sequence.mRepeat; } AiSequence& AiSequence::operator= (const AiSequence& sequence) { if (this!=&sequence) { clear(); copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; } return *this; } AiSequence::~AiSequence() { clear(); } 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(); } std::list>::const_iterator AiSequence::begin() const { return mPackages.begin(); } std::list>::const_iterator AiSequence::end() const { return mPackages.end(); } void AiSequence::erase(std::list>::const_iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? for(auto it = mPackages.begin(); it != mPackages.end(); ++it) { if (package == it) { mPackages.erase(it); return; } } throw std::runtime_error("can't find package to erase"); } bool AiSequence::isInCombat() const { for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) return true; } return false; } bool AiSequence::isEngagedWithActor() const { 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 { for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == typeId) return true; } return false; } bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) { if ((*it)->getTarget() == actor) return true; } } return false; } void AiSequence::stopCombat() { for(auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) { it = mPackages.erase(it); } else ++it; } } void AiSequence::stopPursuit() { for(auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackageTypeId::Pursue) { it = mPackages.erase(it); } else ++it; } } 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()) { if (mPackages.empty()) { mLastAiPackage = AiPackageTypeId::None; return; } auto packageIt = mPackages.begin(); MWMechanics::AiPackage* package = packageIt->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 = mPackages.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; } } assert(!mPackages.empty()); if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) { assert(itActualCombat != mPackages.end()); // move combat package with nearest target to the front mPackages.splice(mPackages.begin(), mPackages, itActualCombat); } packageIt = mPackages.begin(); package = packageIt->get(); packageTypeId = package->getTypeId(); } try { if (package->execute(actor, characterController, mAiState, duration)) { // Put repeating noncombat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) { package->reset(); mPackages.push_back(package->clone()); } // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) mPackages.erase(packageIt); 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(); } 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 = mPackages.erase(it); } else ++it; } mRepeat=false; } // insert new package in correct place depending on priority for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { // We should keep current AiCast package, if we try to add a new one. if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast && package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) { continue; } if((*it)->getPriority() <= package.getPriority()) { mPackages.insert(it, package.clone()); return; } } mPackages.push_back(package.clone()); // Make sure that temporary storage is empty if (cancelOther) { mAiState.moveIn(new AiCombatStorage()); mAiState.moveIn(new AiFollowStorage()); mAiState.moveIn(new AiWanderStorage()); } } bool MWMechanics::AiSequence::isEmpty() const { return mPackages.empty(); } const AiPackage& MWMechanics::AiSequence::getActivePackage() { if(mPackages.empty()) throw std::runtime_error(std::string("No AI Package!")); return *mPackages.front(); } void AiSequence::fill(const ESM::AIPackageList &list) { // If there is more than one package in the list, enable repeating if (list.mList.size() >= 2) mRepeat = true; 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.toString(), data.mDuration, data.mX, data.mY, data.mZ); } else if (esmPackage.mType == ESM::AI_Travel) { ESM::AITravel data = esmPackage.mTravel; package = std::make_unique(data.mX, data.mY, data.mZ); } else if (esmPackage.mType == ESM::AI_Activate) { ESM::AIActivate data = esmPackage.mActivate; package = std::make_unique(data.mName.toString()); } else //if (esmPackage.mType == ESM::AI_Follow) { ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); } 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(); // If there is more than one non-combat, non-pursue package in the list, enable repeating. int count = 0; for (auto& container : sequence.mPackages) { switch (container.mType) { case ESM::AiSequence::Ai_Wander: case ESM::AiSequence::Ai_Travel: case ESM::AiSequence::Ai_Escort: case ESM::AiSequence::Ai_Follow: case ESM::AiSequence::Ai_Activate: ++count; } } if (count > 1) mRepeat = true; // Load packages for (auto& container : sequence.mPackages) { std::unique_ptr package; switch (container.mType) { case ESM::AiSequence::Ai_Wander: { package.reset(new AiWander(static_cast(container.mPackage))); break; } case ESM::AiSequence::Ai_Travel: { const auto source = static_cast(container.mPackage); if (source->mHidden) package.reset(new AiInternalTravel(source)); else package.reset(new AiTravel(source)); break; } case ESM::AiSequence::Ai_Escort: { package.reset(new AiEscort(static_cast(container.mPackage))); break; } case ESM::AiSequence::Ai_Follow: { package.reset(new AiFollow(static_cast(container.mPackage))); break; } case ESM::AiSequence::Ai_Activate: { package.reset(new AiActivate(static_cast(container.mPackage))); break; } case ESM::AiSequence::Ai_Combat: { package.reset(new AiCombat(static_cast(container.mPackage))); break; } case ESM::AiSequence::Ai_Pursue: { package.reset(new AiPursue(static_cast(container.mPackage))); break; } default: break; } if (!package.get()) continue; 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.47.0/apps/openmw/mwmechanics/aisequence.hpp000066400000000000000000000117131413061077700240240ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AISEQUENCE_H #define GAME_MWMECHANICS_AISEQUENCE_H #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; template< class Base > class DerivedClassStorage; struct AiTemporaryBase; typedef DerivedClassStorage AiState; /// \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 std::list> mPackages; ///Finished with top AIPackage, set for one frame bool mDone; ///Does this AI sequence repeat (repeating of Wander packages handled separately) bool mRepeat; ///Copy AiSequence void copy (const AiSequence& sequence); /// The type of AI package that ran last AiPackageTypeId mLastAiPackage; AiState mAiState; 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(). std::list>::const_iterator begin() const; std::list>::const_iterator end() const; void erase(std::list>::const_iterator package); /// 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; /// 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(); /// 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(); /// 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.47.0/apps/openmw/mwmechanics/aistate.hpp000066400000000000000000000055201413061077700233330ustar00rootroot00000000000000#ifndef AISTATE_H #define AISTATE_H #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: Base* mStorage; //if needed you have to provide a clone member function DerivedClassStorage( const DerivedClassStorage& other ); DerivedClassStorage& operator=( const DerivedClassStorage& ); public: /// \brief returns reference to stored object or deletes it and creates a fitting template< class Derived > Derived& get() { Derived* result = dynamic_cast(mStorage); if(!result) { if(mStorage) delete mStorage; mStorage = result = new Derived(); } //return a reference to the (new allocated) object return *result; } template< class Derived > void copy(DerivedClassStorage& destination) const { Derived* result = dynamic_cast(mStorage); if (result != nullptr) destination.store(*result); } template< class Derived > void store( const Derived& payload ) { if(mStorage) delete mStorage; mStorage = new Derived(payload); } /// \brief takes ownership of the passed object template< class Derived > void moveIn( Derived* p ) { if(mStorage) delete mStorage; mStorage = p; } bool empty() const { return mStorage == nullptr; } const std::type_info& getType() const { return typeid(mStorage); } DerivedClassStorage():mStorage(nullptr){} ~DerivedClassStorage() { if(mStorage) delete mStorage; } }; /// \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(){} }; /// \brief Container for AI package status. typedef DerivedClassStorage AiState; } #endif // AISTATE_H openmw-openmw-0.47.0/apps/openmw/mwmechanics/aitimer.hpp000066400000000000000000000012511413061077700233300ustar00rootroot00000000000000#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; Misc::TimerStatus update(float duration) { return mImpl.update(duration); } void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } private: Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, Misc::Rng::deviate(0, sDeviation)}; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/aitravel.cpp000066400000000000000000000114021413061077700234770ustar00rootroot00000000000000#include "aitravel.hpp" #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 { 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, AiTravel*) : mX(x), mY(y), mZ(z), mHidden(false) { } AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived) : TypedAiPackage(derived), mX(x), mY(y), mZ(z), mHidden(true) { } AiTravel::AiTravel(float x, float y, float z) : AiTravel(x, y, z, this) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) { // 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; // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. int destinationTolerance = 64; if (distance(actorPos, targetPos) <= destinationTolerance) { std::vector targetActors; std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); if (!result.first.isEmpty()) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } } if (pathTo(actor, targetPos, duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } return false; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), 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, mX, mY, mZ); actor.getClass().adjustPosition(actor, false); reset(); } void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr travel(new ESM::AiSequence::AiTravel()); travel->mData.mX = mX; travel->mData.mY = mY; travel->mData.mZ = mZ; travel->mHidden = mHidden; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; package.mPackage = travel.release(); sequence.mPackages.push_back(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.47.0/apps/openmw/mwmechanics/aitravel.hpp000066400000000000000000000037161413061077700235150ustar00rootroot00000000000000#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, AiTravel* derived); AiTravel(float x, float y, float z, AiInternalTravel* derived); AiTravel(float x, float y, float z); 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; }; 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.47.0/apps/openmw/mwmechanics/aiwander.cpp000066400000000000000000001142751413061077700234760ustar00rootroot00000000000000#include "aiwander.hpp" #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) { const float randomDirection = Misc::Rng::rollClosedProbability() * 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()->getPathfindingHalfExtents(actor); 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); } bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) { const auto world = MWBase::Environment::get().getWorld(); const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); } void stopMovement(const MWWorld::Ptr& actor) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).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)); } } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): TypedAiPackage(makeDefaultOptions().withRepeat(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 osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); } 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); } if (canActorMoveByZAxis(actor) && mDistance > 0) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 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) >= 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 halfExtents = world->getPathfindingHalfExtents(actor); const auto navigator = world->getNavigator(); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); do { // Determine a random location within radius of original position const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, mInitialActorPosition, wanderDistance, navigatorFlags)) mDestination = *destination; else mDestination = 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; if (isWaterCreature || isFlyingCreature) mPathFinder.buildStraightPath(mDestination); else mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags, areaCosts); 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 halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, 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) { unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); 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; } } short unsigned AiWander::getRandomIdle() { unsigned short idleRoll = 0; short unsigned selectedAnimation = 0; for(unsigned int counter = 0; counter < mIdle.size(); counter++) { static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("fIdleChanceMultiplier")->mValue.getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { selectedAnimation = counter + GroupIndex_MinIdle; idleRoll = randSelect; } } return selectedAnimation; } 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; int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); 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()); 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(new AiWanderStorage()); MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); 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; std::unique_ptr wander(new ESM::AiSequence::AiWander()); 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 = wander.release(); sequence.mPackages.push_back(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.47.0/apps/openmw/mwmechanics/aiwander.hpp000066400000000000000000000160711413061077700234760ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIWANDER_H #define GAME_MWMECHANICS_AIWANDER_H #include "typedaipackage.hpp" #include #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.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(): mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), mIdleAnimation(0), mBadIdles(), mPopulateAvailableNodes(true), mAllowedNodes(), mTrimCurrentNode(false), mCheckIdlePositionTimer(0), mStuckCount(0) {}; 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); short unsigned getRandomIdle(); 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.47.0/apps/openmw/mwmechanics/alchemy.cpp000066400000000000000000000427121413061077700233220ustar00rootroot00000000000000#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; int index = Misc::Rng::rollDice(6); 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; } if (getAlchemyFactor() < Misc::Rng::roll0to99()) { 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.47.0/apps/openmw/mwmechanics/alchemy.hpp000066400000000000000000000113511413061077700233220ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/autocalcspell.cpp000066400000000000000000000301301413061077700245220ustar00rootroot00000000000000#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(); if (baseMagicka < iAutoSpellTimesCanCast * spell.mData.mCost) 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 && spell.mData.mCost <= 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 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 && testSpell->mData.mCost < cap.mMinCost) { cap.mMinCost = testSpell->mData.mCost; cap.mWeakestSpell = testSpell->mId; } } } else { cap.mCount += 1; if (cap.mCount == cap.mLimit) cap.mReachedLimit = true; if (spell.mData.mCost < cap.mMinCost) { cap.mWeakestSpell = spell.mId; cap.mMinCost = spell.mData.mCost; } } } 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; if (reachedLimit && spell.mData.mCost <= minCost) continue; if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end()) continue; if (baseMagicka < spell.mData.mCost) 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); if (testSpell->mData.mCost < minCost) { minCost = testSpell->mData.mCost; weakestSpell = testSpell; } } } else { if (spell.mData.mCost < minCost) { weakestSpell = &spell; minCost = weakestSpell->mData.mCost; } 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 - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; return castChance; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/autocalcspell.hpp000066400000000000000000000017041413061077700245340ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/character.cpp000066400000000000000000003534351413061077700236430ustar00rootroot00000000000000/* * 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 "../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 "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])/2; int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; 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. MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) { using namespace MWMechanics; CharacterState ret = state; switch (state) { case CharState_RunForward: ret = CharState_WalkForward; break; case CharState_RunBack: ret = CharState_WalkBack; break; case CharState_RunLeft: ret = CharState_WalkLeft; break; case CharState_RunRight: ret = CharState_WalkRight; break; case CharState_SwimRunForward: ret = CharState_SwimWalkForward; break; case CharState_SwimRunBack: ret = CharState_SwimWalkBack; break; case CharState_SwimRunLeft: ret = CharState_SwimWalkLeft; break; case CharState_SwimRunRight: ret = CharState_SwimWalkRight; break; default: break; } return ret; } 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; } } namespace MWMechanics { struct StateInfo { CharacterState state; const char groupname[32]; }; static const StateInfo sMovementList[] = { { CharState_WalkForward, "walkforward" }, { CharState_WalkBack, "walkback" }, { CharState_WalkLeft, "walkleft" }, { CharState_WalkRight, "walkright" }, { CharState_SwimWalkForward, "swimwalkforward" }, { CharState_SwimWalkBack, "swimwalkback" }, { CharState_SwimWalkLeft, "swimwalkleft" }, { CharState_SwimWalkRight, "swimwalkright" }, { CharState_RunForward, "runforward" }, { CharState_RunBack, "runback" }, { CharState_RunLeft, "runleft" }, { CharState_RunRight, "runright" }, { CharState_SwimRunForward, "swimrunforward" }, { CharState_SwimRunBack, "swimrunback" }, { CharState_SwimRunLeft, "swimrunleft" }, { CharState_SwimRunRight, "swimrunright" }, { CharState_SneakForward, "sneakforward" }, { CharState_SneakBack, "sneakback" }, { CharState_SneakLeft, "sneakleft" }, { CharState_SneakRight, "sneakright" }, { CharState_Jump, "jump" }, { CharState_TurnLeft, "turnleft" }, { CharState_TurnRight, "turnright" }, { CharState_SwimTurnLeft, "swimturnleft" }, { CharState_SwimTurnRight, "swimturnright" }, }; static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; class FindCharState { CharacterState state; public: FindCharState(CharacterState _state) : state(_state) { } bool operator()(const StateInfo &info) const { return info.state == state; } }; std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { int numAnims=0; while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1))) ++numAnims; int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] if (num) *num = roll; return prefix + std::to_string(roll); } void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); if(mHitState == CharState_None) { if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)) { mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds if (isSwimming && mAnimation->hasAnimation("swimknockout")) { mHitState = CharState_SwimKnockOut; mCurrentHit = "swimknockout"; mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); } else if (!isSwimming && mAnimation->hasAnimation("knockout")) { mHitState = CharState_KnockOut; mCurrentHit = "knockout"; mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); } else { // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH. mCurrentHit.erase(); } mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } else if (knockdown) { if (isSwimming && mAnimation->hasAnimation("swimknockdown")) { mHitState = CharState_SwimKnockDown; mCurrentHit = "swimknockdown"; mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else if (!isSwimming && mAnimation->hasAnimation("knockdown")) { mHitState = CharState_KnockDown; mCurrentHit = "knockdown"; mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else { // Knockdown animation is missing. Cancel knockdown state. mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); } } else if (recovery) { std::string anim = chooseRandomGroup("swimhit"); if (isSwimming && mAnimation->hasAnimation(anim)) { mHitState = CharState_SwimHit; mCurrentHit = anim; mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else { anim = chooseRandomGroup("hit"); if (mAnimation->hasAnimation(anim)) { mHitState = CharState_Hit; mCurrentHit = anim; mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } } } else if (block && mAnimation->hasAnimation("shield")) { mHitState = CharState_Block; mCurrentHit = "shield"; MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; priorityBlock[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); } // Cancel upper body animations if (isKnockedOut() || isKnockedDown()) { if (mUpperBodyState > UpperCharState_WeapEquiped) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_Nothing; } } if (mHitState != CharState_None) idle = CharState_None; } else if(!mAnimation->isPlaying(mCurrentHit)) { mCurrentHit.erase(); if (knockdown) mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); if (recovery) mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); if (block) mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mHitState = CharState_None; } else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 && mTimeUntilWake <= 0) { mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; mAnimation->disable(mCurrentHit); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } } void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) { if (!force && jump == mJumpState && idle == CharState_None) return; std::string jumpAnimName; MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; if (jump != JumpState_None) { jumpAnimName = "jump"; if(!weapShortGroup.empty()) { jumpAnimName += weapShortGroup; if(!mAnimation->hasAnimation(jumpAnimName)) { jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); // If we apply jump only for lower body, do not reset idle animations. // For upper body there will be idle animation. if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; } } } if (!force && jump == mJumpState) return; bool startAtLoop = (jump == mJumpState); mJumpState = jump; if (!mCurrentJump.empty()) { mAnimation->disable(mCurrentJump); mCurrentJump.clear(); } if(mJumpState == JumpState_InAir) { if (mAnimation->hasAnimation(jumpAnimName)) { mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); mCurrentJump = jumpAnimName; } } else if (mJumpState == JumpState_Landing) { if (mAnimation->hasAnimation(jumpAnimName)) { mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); mCurrentJump = jumpAnimName; } } } bool CharacterController::onOpen() { if (mPtr.getTypeName() == typeid(ESM::Container).name()) { 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() { if (mPtr.getTypeName() == typeid(ESM::Container).name()) { 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 CharacterController::getWeaponAnimation(int weaponType) const { std::string weaponGroup = getWeaponType(weaponType)->mLongGroup; bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None; if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup)) { static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; static const std::string 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 if (isRealWeapon) weaponGroup = oneHandFallback; } return weaponGroup; } std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) { bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; if (!isRealWeapon) { if (blendMask != nullptr) *blendMask = MWRender::Animation::BlendMask_LowerBody; return baseGroupName; } static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup; static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup; 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(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force) { if (movement == mMovementState && idle == mIdleState && !force) return; // Reset idle if we actually play movement animations excepts of these cases: // 1. When we play turning animations // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting) bool resetIdle = (movement != CharState_None && !isTurning()); std::string movementAnimName; MWRender::Animation::BlendMask movemask; const StateInfo *movestate; movemask = MWRender::Animation::BlendMask_All; movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(movement)); if(movestate != sMovementListEnd) { movementAnimName = movestate->groupname; if(!weapShortGroup.empty()) { std::string::size_type swimpos = movementAnimName.find("swim"); if (swimpos == std::string::npos) { if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case movementAnimName = weapShortGroup + movementAnimName; else movementAnimName += weapShortGroup; } if(!mAnimation->hasAnimation(movementAnimName)) { movementAnimName = movestate->groupname; if (swimpos == std::string::npos) { movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); // If we apply movement only for lower body, do not reset idle animations. // For upper body there will be idle animation. if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; if (movemask == MWRender::Animation::BlendMask_LowerBody) resetIdle = false; } } } } if(force || movement != mMovementState) { mMovementState = movement; if(movestate != sMovementListEnd) { if(!mAnimation->hasAnimation(movementAnimName)) { std::string::size_type swimpos = movementAnimName.find("swim"); if (swimpos != std::string::npos) { movementAnimName.erase(swimpos, 4); if (!weapShortGroup.empty()) { std::string weapMovementAnimName = movementAnimName + weapShortGroup; if(mAnimation->hasAnimation(weapMovementAnimName)) movementAnimName = weapMovementAnimName; else { movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); if (movemask == MWRender::Animation::BlendMask_LowerBody) resetIdle = false; } } } if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName)) { std::string::size_type runpos = movementAnimName.find("run"); if (runpos != std::string::npos) { movementAnimName.replace(runpos, runpos+3, "walk"); if (!mAnimation->hasAnimation(movementAnimName)) movementAnimName.clear(); } else movementAnimName.clear(); } } } // 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; mAnimation->disable(mCurrentMovement); if (!mAnimation->hasAnimation(movementAnimName)) movementAnimName.clear(); mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { if (resetIdle) { mAnimation->disable(mCurrentIdle); mIdleState = CharState_None; idle = CharState_None; } // 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. std::string anim = mCurrentMovement; mAdjustMovementAnimSpeed = true; if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { CharacterState walkState = runStateToWalkState(mMovementState); const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); anim = stateinfo->groupname; 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(anim); 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); } else mMovementState = CharState_None; } } void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, 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 && !isTurning()) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) idle = CharState_None; if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) { mIdleState = idle; size_t numLoops = ~0ul; std::string idleGroup; MWRender::Animation::AnimPriority idlePriority (Priority_Default); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) { idleGroup = "idleswim"; idlePriority = Priority_SwimIdle; } else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) { idleGroup = "idlesneak"; idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; } else if(mIdleState != CharState_None) { idleGroup = "idle"; if(!weapShortGroup.empty()) { idleGroup += weapShortGroup; if(!mAnimation->hasAnimation(idleGroup)) { idleGroup = fallbackShortWeaponGroup("idle"); } // 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 numLoops = 1 + Misc::Rng::rollDice(4); } } // There is no need to restart anim if the new and old anims are the same. // Just update a number of loops. float startPoint = 0; if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup) { mAnimation->getInfo(mCurrentIdle, &startPoint); } if(!mCurrentIdle.empty()) mAnimation->disable(mCurrentIdle); mCurrentIdle = idleGroup; if(!mCurrentIdle.empty()) mAnimation->play(mCurrentIdle, idlePriority, 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; if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); std::string weap; if (mPtr.getClass().hasInventoryStore(mPtr)) weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); // idle handled last as it can depend on the other states refreshIdleAnims(weap, idle, force); } void CharacterController::playDeath(float startpoint, CharacterState death) { // Make sure the character was swimming upon death for forward-compatibility const bool wasSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); switch (death) { case CharState_SwimDeath: mCurrentDeath = "swimdeath"; break; case CharState_SwimDeathKnockDown: mCurrentDeath = (wasSwimming ? "swimdeathknockdown" : "deathknockdown"); break; case CharState_SwimDeathKnockOut: mCurrentDeath = (wasSwimming ? "swimdeathknockout" : "deathknockout"); break; case CharState_DeathKnockDown: mCurrentDeath = "deathknockdown"; break; case CharState_DeathKnockOut: mCurrentDeath = "deathknockout"; break; default: mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1); } mDeathState = death; 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. mMovementState = CharState_None; mAnimation->disable(mCurrentMovement); mCurrentMovement = ""; mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); mCurrentWeapon = ""; mHitState = CharState_None; mAnimation->disable(mCurrentHit); mCurrentHit = ""; mIdleState = CharState_None; mAnimation->disable(mCurrentIdle); mCurrentIdle = ""; mJumpState = JumpState_None; mAnimation->disable(mCurrentJump); mCurrentJump = ""; 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(); } if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) { mDeathState = CharState_SwimDeathKnockDown; } else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) { mDeathState = CharState_SwimDeathKnockOut; } else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) { mDeathState = CharState_SwimDeath; } else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) { mDeathState = CharState_DeathKnockDown; } else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) { mDeathState = CharState_DeathKnockOut; } else { 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) , mWeapon(MWWorld::Ptr()) , mAnimation(anim) , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementAnimSpeed(0.f) , mAdjustMovementAnimSpeed(false) , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) , mFloatToSurface(true) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) , mWeaponType(ESM::Weapon::None) , mAttackStrength(0.f) , mSkipAnim(false) , mSecondsOfSwimming(0) , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) , mAttackingOrSpell(false) , mCastingManualSpell(false) , mTimeUntilWake(0.f) , mIsMovingBackward(false) { 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 split(const std::string &s, char delim, std::vector &elems) { std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } } void CharacterController::handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { const std::string &evt = key->second; if(evt.compare(0, 7, "sound: ") == 0) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); return; } if(evt.compare(0, 10, "soundgen: ") == 0) { std::string soundgen = 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; 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 = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); // NB: landing sound is not played for NPCs here if(soundgen == "left" || soundgen == "right" || soundgen == "land") { sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } else { sndMgr->playSound3D(mPtr, sound, volume, pitch); } } return; } if(evt.compare(0, groupname.size(), groupname) != 0 || evt.compare(groupname.size(), 2, ": ") != 0) { // Not ours, skip it return; } size_t off = groupname.size()+2; size_t len = evt.size() - off; if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0) mAnimation->showCarriedLeft(true); else if(groupname == "shield" && evt.compare(off, len, "unequip detach") == 0) mAnimation->showCarriedLeft(false); else if(evt.compare(off, len, "equip attach") == 0) mAnimation->showWeapons(true); else if(evt.compare(off, len, "unequip detach") == 0) mAnimation->showWeapons(false); else if(evt.compare(off, len, "chop hit") == 0) mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if(evt.compare(off, len, "slash hit") == 0) mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if(evt.compare(off, len, "thrust hit") == 0) mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) { if (groupname == "attack1" || groupname == "swimattack1") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else mPtr.getClass().hit(mPtr, mAttackStrength); } else if (!groupname.empty() && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) && evt.compare(off, len, "start") == 0) { std::multimap::const_iterator hitKey = key; // 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 == groupname + ": hit") { hasHitKey = true; break; } if (hitKey->second == groupname + ": stop") break; ++hitKey; } if (!hasHitKey) { if (groupname == "attack1" || groupname == "swimattack1") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); } } else if (evt.compare(off, len, "shoot attach") == 0) mAnimation->attachArrow(); else if (evt.compare(off, len, "shoot release") == 0) mAnimation->releaseArrow(mAttackStrength); else if (evt.compare(off, len, "shoot follow attach") == 0) mAnimation->attachArrow(); else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" // 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. && evt.compare(off, len, mAttackType + " release") == 0) { MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); mCastingManualSpell = false; } else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) mPtr.getClass().block(mPtr); else if (groupname == "containeropen" && evt.compare(off, len, "loot") == 0) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr); } void CharacterController::updatePtr(const MWWorld::Ptr &ptr) { mPtr = ptr; } void CharacterController::updateIdleStormState(bool inwater) { if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater) { mAnimation->disable("idlestorm"); return; } if (MWBase::Environment::get().getWorld()->isInStorm()) { osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->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::updateCreatureState() { 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; if (weapType != mWeaponType) { mWeaponType = weapType; if (mAnimation->isPlaying(mCurrentWeapon)) mAnimation->disable(mCurrentWeapon); } if(mAttackingOrSpell) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); std::string startKey = "start"; std::string stopKey = "stop"; if (weapType == ESM::Weapon::Spell) { const std::string spellid = stats.getSpells().getSelectedSpell(); bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); if (!spellid.empty() && canCast) { MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid, false); if (!mAnimation->hasAnimation("spellcast")) { MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately mCastingManualSpell = false; } else { const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); switch(effectentry.mRange) { case 0: mAttackType = "self"; break; case 1: mAttackType = "touch"; break; case 2: mAttackType = "target"; break; } startKey = mAttackType + " " + startKey; stopKey = mAttackType + " " + stopKey; mCurrentWeapon = "spellcast"; } } else mCurrentWeapon = ""; } if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { mCurrentWeapon = chooseRandomAttackAnimation(); } if (!mCurrentWeapon.empty()) { mAnimation->play(mCurrentWeapon, Priority_Weapon, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); if (weapType == ESM::Weapon::HandToHand) playSwishSound(0.0f); } } mAttackingOrSpell = false; } bool animPlaying = mAnimation->getInfo(mCurrentWeapon); if (!animPlaying) mUpperBodyState = UpperCharState_Nothing; return 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 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 (mPtr.getClass().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 (mPtr.getClass().isBipedal(mPtr)) priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition 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; mUpperBodyState = UpperCharState_WeapEquiped; mAttackingOrSpell = false; mAnimation->disable(mCurrentWeapon); mAnimation->showWeapons(true); if (mPtr == getPlayer()) MWBase::Environment::get().getWorld()->getPlayer().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()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); 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) { mAnimation->disable(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 = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); if(sound) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } } mWeaponType = weaptype; mCurrentWeapon = getWeaponAnimation(mWeaponType); if(!upSoundId.empty() && !isStillWeapon) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); } } // Make sure that we disabled unequipping animation if (mUpperBodyState == UpperCharState_UnEquipingWeap) { mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); mWeaponType = ESM::Weapon::None; mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } if(isWerewolf) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && mHasMovedInXY && !MWBase::Environment::get().getWorld()->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 (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); 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) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } } // Combat for actors with persistent animations obviously will be buggy if (isPersistentAnimPlaying()) return forcestateupdate; float complete; bool animPlaying; ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(mAttackingOrSpell) { MWWorld::Ptr player = getPlayer(); bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; // Randomize attacks for non-bipedal creatures with Weapon flag if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && !mPtr.getClass().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 mAttackingOrSpell = false; if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); // 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; bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); if (spellid.empty()) { if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = mPtr.getClass().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) { // Enchanted items by default do not use casting animations MWBase::Environment::get().getWorld()->castSpell(mPtr); resetIdle = false; } else if(!spellid.empty() && canCast) { MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid, isMagicItem); std::vector effects; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->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; } const ESM::MagicEffect *effect = store.get().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect { if (mAnimation->getNode("Bip01 L Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); if (mAnimation->getNode("Bip01 R Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -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"; MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately mCastingManualSpell = 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, true, 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } else { resetIdle = false; } } else if(mWeaponType == ESM::Weapon::PickProbe) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().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 = MWBase::Environment::get().getWorld()->getFacedObject(); std::string resultMessage, resultSound; if(!target.isEmpty()) { if(item.getTypeName() == typeid(ESM::Lockpick).name()) Security(mPtr).pickLock(target, item, resultMessage, resultSound); else if(item.getTypeName() == typeid(ESM::Probe).name()) Security(mPtr).probeTrap(target, item, resultMessage, resultSound); } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, true, 1.0f, "start", "stop", 0.0, 0); mUpperBodyState = UpperCharState_FollowStartToFollowStop; if(!resultMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); if(!resultSound.empty()) MWBase::Environment::get().getSoundManager()->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 = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); } else { // There is no "best attack" for Hand-to-Hand setAttackTypeRandomly(mAttackType); } } else { setAttackTypeBasedOnMovement(); } } // 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); mUpperBodyState = UpperCharState_StartToMinAttack; } } // We should not break swim and sneak animations if (resetIdle && idle != CharState_IdleSneak && idle != CharState_IdleSwim && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { mAnimation->disable(mCurrentIdle); mIdleState = CharState_None; } animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) mAttackStrength = complete; } else { animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) { 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()); } if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); 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); } 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) { if(mUpperBodyState == UpperCharState_EquipingWeap || mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); mUpperBodyState = UpperCharState_WeapEquiped; } else if(mUpperBodyState == UpperCharState_UnEquipingWeap) mUpperBodyState = UpperCharState_Nothing; } else 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 (mAttackingOrSpell || minAttackTime == maxAttackTime) { start = mAttackType+" min attack"; stop = mAttackType+" max attack"; mUpperBodyState = UpperCharState_MinAttackToMaxAttack; break; } 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); if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) mAnimation->play(mCurrentWeapon, priorityWeapon, mask, true, weapSpeed, start, stop, 0.0f, 0); else mAnimation->play(mCurrentWeapon, priorityWeapon, mask, false, weapSpeed, start, stop, 0.0f, 0); } } else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } if (mPtr.getClass().hasInventoryStore(mPtr)) { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && 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(); const MWWorld::Class &cls = mPtr.getClass(); osg::Vec3f movement(0.f, 0.f, 0.f); float speed = 0.f; updateMagicEffects(); if (isKnockedOut()) mTimeUntilWake -= duration; 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; 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 = osg::clampBetween(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 && mJumpState == JumpState_None) vec = osg::Vec3f(); CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; JumpingState jumpstate = JumpState_None; bool forcestateupdate = false; 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). forcestateupdate = (mJumpState != JumpState_InAir); 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 && !inwater && !flying && solid) { forcestateupdate = true; jumpstate = JumpState_Landing; vec.z() = 0.0f; // We should reset idle animation during landing mAnimation->disable(mCurrentIdle); float height = cls.getCreatureStats(mPtr).land(isPlayer); float healthLost = getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); // inflict fall damages if (!godmode) { float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); } 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; } else { if(mPtr.getClass().isNpc() && mJumpState == JumpState_InAir && !flying && solid) playLandingSound = true; jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; 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 && jumpstate == JumpState_None && !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) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); 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 += osg::clampBetween(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 && !isTurning()) clearAnimQueue(); 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) { // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; else forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); 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.x(), rot.y(), rot.z(), true); } else //avoid z-rotating for knockdown { if (rot.x() != 0 && rot.y() != 0) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, 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() { 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::max(start, std::min(stop, anim.mTime)); complete = (time - start) / (stop - start); } mAnimation->disable(mCurrentIdle); mCurrentIdle.clear(); 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 false; // 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); mAnimation->disable(mCurrentIdle); mCurrentIdle.clear(); 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() { if (!mAnimQueue.empty()) { AnimationQueueEntry& first = mAnimQueue.front(); return first.mPersist && isAnimPlaying(first.mGroup); } return false; } bool CharacterController::isAnimPlaying(const std::string &groupName) { 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); for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { if (clearPersistAnims || !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. mCastingManualSpell = false; mAttackingOrSpell = 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(); mAnimation->disable(mCurrentIdle); mIdleState = CharState_None; mCurrentIdle.clear(); 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; if(mAnimation) mAnimation->disable(mCurrentDeath); mCurrentDeath.clear(); mDeathState = CharState_None; mWeaponType = ESM::Weapon::None; } void CharacterController::updateContinuousVfx() { // 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() { 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) { // 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::min(0.75f, std::max(0.25f, (100.f - chameleon)/100.f)); } visibility = std::min(visibility, alpha); } // TODO: implement a dithering shader rather than just change object transparency. mAnimation->setAlpha(visibility); } void CharacterController::setAttackTypeBasedOnMovement() { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward mAttackType = "thrust"; else if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway mAttackType = "slash"; else mAttackType = "chop"; } bool CharacterController::isRandomAttackAnimation(const std::string& group) const { 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) { mAttackingOrSpell = attackingOrSpell; } void CharacterController::castSpell(const std::string spellId, bool manualSpell) { mAttackingOrSpell = true; mCastingManualSpell = manualSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); } void CharacterController::setAIAttackType(const std::string& attackType) { mAttackType = attackType; } void CharacterController::setAttackTypeRandomly(std::string& attackType) { float random = Misc::Rng::rollProbability(); if (random >= 2/3.f) attackType = "thrust"; else if (random >= 1/3.f) attackType = "slash"; else attackType = "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; if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) return mUpperBodyState == UpperCharState_WeapEquiped; else return mUpperBodyState == UpperCharState_Nothing; } float CharacterController::getAttackStrength() const { return mAttackStrength; } void CharacterController::setActive(int active) { mAnimation->setActive(active); } void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) { mHeadTrackTarget = target; } void CharacterController::playSwishSound(float attackStrength) { 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 = osg::clampBetween(xAngleRadians, -xLimit, xLimit); zAngleRadians = osg::clampBetween(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.47.0/apps/openmw/mwmechanics/character.hpp000066400000000000000000000172531413061077700236430ustar00rootroot00000000000000#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_Idle2, CharState_Idle3, CharState_Idle4, CharState_Idle5, CharState_Idle6, CharState_Idle7, CharState_Idle8, CharState_Idle9, 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_Jump, 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; std::string mCurrentIdle; CharacterState mMovementState; std::string mCurrentMovement; float mMovementAnimSpeed; bool mAdjustMovementAnimSpeed; bool mHasMovedInXY; bool mMovementAnimationControlled; CharacterState mDeathState; std::string mCurrentDeath; bool mFloatToSurface; CharacterState mHitState; std::string mCurrentHit; UpperBodyCharacterState mUpperBodyState; JumpingState mJumpState; std::string mCurrentJump; int mWeaponType; std::string mCurrentWeapon; float mAttackStrength; bool mSkipAnim; // counted for skill increase float mSecondsOfSwimming; float mSecondsOfRunning; MWWorld::ConstPtr mHeadTrackTarget; float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust bool mAttackingOrSpell; bool mCastingManualSpell; float mTimeUntilWake; bool mIsMovingBackward; osg::Vec2f mSmoothedSpeed; void setAttackTypeBasedOnMovement(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false); void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(CharacterState& idle); bool updateCreatureState(); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; bool isRandomAttackAnimation(const std::string& group) const; bool isPersistentAnimPlaying(); void updateAnimQueue(); void updateHeadTracking(float duration); void updateMagicEffects(); 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); std::string getWeaponAnimation(int weaponType) const; public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); void handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) override; // Be careful when to call this, see comment in Actors void updateContinuousVfx(); void updatePtr(const MWWorld::Ptr &ptr); void update(float duration); bool onOpen(); void onClose(); void persistAnimationState(); void unpersistAnimationState(); bool playGroup(const std::string &groupname, int mode, int count, bool persist=false); void skipAnim(); bool isAnimPlaying(const std::string &groupName); 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); void setAttackingOrSpell(bool attackingOrSpell); void castSpell(const std::string spellId, bool manualSpell=false); void setAIAttackType(const std::string& attackType); static void setAttackTypeRandomly(std::string& attackType); bool readyToPrepareAttack() const; bool readyToStartAttack() const; float getAttackStrength() const; /// @see Animation::setActive void setActive(int active); /// 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); }; } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ openmw-openmw-0.47.0/apps/openmw/mwmechanics/combat.cpp000066400000000000000000000567411413061077700231540ustar00rootroot00000000000000#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, false); 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->getTypeName() != typeid(ESM::Armor).name()) 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(); if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->mValue.getFloat()) return false; if (angleDegrees > gmst.find("fCombatBlockRightAngle")->mValue.getFloat()) 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; float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->mValue.getFloat() + gmst.find("fSwingBlockBase")->mValue.getFloat(); float blockerTerm = blockTerm * swingTerm; if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat(); 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(); int x = int(blockerTerm - attackerTerm); int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); if (Misc::Rng::roll0to99() < 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 const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); 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(); float damage = 0.f; if (validVictim) { if (attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weaponSkill = ESM::Skill::Marksman; if (!weapon.isEmpty()) weaponSkill = weapon.getClass().getEquipmentSkill(weapon); int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); if (Misc::Rng::roll0to99() >= 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); 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) { damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat(); if (!knockedDown) MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } } reduceWeaponCondition(damage, validVictim, weapon, attacker); // 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) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); if (Misc::Rng::rollProbability() < 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(); } defenseTerm += std::min(100.f, gmst.find("fCombatInvisoMult")->mValue.getFloat() * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, gmst.find("fCombatInvisoMult")->mValue.getFloat() * 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; 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()); 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(); float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); 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) damage *= store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) { const ESM::Sound *sound = store.get().searchRandom("WolfHit"); 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(); const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); 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); } bool isTargetMagicallyHidden(const MWWorld::Ptr& target) { const MagicEffects& magicEffects = target.getClass().getCreatureStats(target).getMagicEffects(); return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); } 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.47.0/apps/openmw/mwmechanics/combat.hpp000066400000000000000000000054451413061077700231540ustar00rootroot00000000000000#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); bool isTargetMagicallyHidden(const MWWorld::Ptr& target); float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/creaturestats.cpp000066400000000000000000000505001413061077700245630ustar00rootroot00000000000000#include "creaturestats.hpp" #include #include #include #include #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), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; } 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 = 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[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) mRecalcMagicka = true; 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 diff = (strength+willpower+agility+endurance) - fatigue.getBase(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; fatigue.setModified(fatigue.getModified() + diff, 0); fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio); 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].setModifier(0); mDynamic[index].setCurrentModifier(0); mDynamic[index].setCurrent(0); } } void CreatureStats::setLevel(int level) { mLevel = level; } void CreatureStats::modifyMagicEffects(const MagicEffects &effects) { if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) mRecalcMagicka = true; mMagicEffects.setModifiers(effects); } void CreatureStats::setAiSetting (AiSetting index, Stat value) { mAiSettings[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) { if (mDynamic[0].getModified() < 1) mDynamic[0].setModified(1, 0); mDynamic[0].setCurrent(mDynamic[0].getModified()); 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; } const std::string &CreatureStats::getLastHitObject() const { return mLastHitObject; } void CreatureStats::setLastHitAttemptObject(const std::string& objectid) { mLastHitAttemptObject = objectid; } 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; } bool CreatureStats::needToRecalcDynamicStats() { if (mRecalcMagicka) { mRecalcMagicka = false; return true; } return false; } void CreatureStats::setNeedRecalcDynamicStats(bool val) { mRecalcMagicka = val; } 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; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); } } void CreatureStats::readState (const ESM::CreatureStats& state) { for (int i=0; i 0) mBoundItems.insert(effectId); else { // Check active spell effects // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) { const auto& effects = spell.second.mEffects; return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) { return effect.mEffectId == effectId; }) != effects.end(); }); if(spell != mActiveSpells.end()) mBoundItems.insert(effectId); } } mSummonedCreatures = state.mSummonedCreatureMap; mSummonGraveyard = state.mSummonGraveyard; if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); mCorprusSpells.clear(); for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) { for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); } } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) { mLastRestock = tradeTime; } MWWorld::TimeStamp CreatureStats::getLastRestockTime() const { return mLastRestock; } void CreatureStats::setGoldPool(int pool) { mGoldPool = pool; } int CreatureStats::getGoldPool() const { return mGoldPool; } int CreatureStats::getActorId() { if (mActorId==-1) mActorId = sActorId++; return mActorId; } bool CreatureStats::matchesActorId (int id) const { return mActorId!=-1 && id==mActorId; } void CreatureStats::cleanup() { sActorId = 0; } void CreatureStats::writeActorIdCounter (ESM::ESMWriter& esm) { esm.startRecord(ESM::REC_ACTC); esm.writeHNT("COUN", sActorId); esm.endRecord(ESM::REC_ACTC); } void CreatureStats::readActorIdCounter (ESM::ESMReader& esm) { esm.getHNT(sActorId, "COUN"); } signed char CreatureStats::getDeathAnimation() const { return mDeathAnimation; } void CreatureStats::setDeathAnimation(signed char index) { mDeathAnimation = index; } MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const { return mTimeOfDeath; } std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } std::vector& CreatureStats::getSummonedCreatureGraveyard() { return mSummonGraveyard; } std::map &CreatureStats::getCorprusSpells() { return mCorprusSpells; } void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats) { mCorprusSpells[sourceId] = stats; } void CreatureStats::removeCorprusSpell(const std::string& sourceId) { auto corprusIt = mCorprusSpells.find(sourceId); if (corprusIt != mCorprusSpells.end()) { mCorprusSpells.erase(corprusIt); } } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/creaturestats.hpp000066400000000000000000000223041413061077700245710ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_CREATURESTATS_H #define GAME_MWMECHANICS_CREATURESTATS_H #include #include #include #include "stat.hpp" #include "magiceffects.hpp" #include "spells.hpp" #include "activespells.hpp" #include "aisequence.hpp" #include "drawstate.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 bool mRecalcMagicka; // 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::map 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; std::map mCorprusSpells; protected: int mLevel; public: CreatureStats(); DrawState_ getDrawState() const; void setDrawState(DrawState_ state); bool needToRecalcDynamicStats(); void setNeedRecalcDynamicStats(bool val); 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; 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); void setLevel(int level); enum AiSetting { AI_Hello = 0, AI_Fight = 1, AI_Flee = 2, AI_Alarm = 3 }; 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::map& 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); const std::string &getLastHitObject() const; void setLastHitAttemptObject(const std::string &objectid); const std::string &getLastHitAttemptObject() const; void setHitAttemptActorId(const int actorId); int getHitAttemptActorId() const; // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. // TODO: Put it somewhere else? std::set mBoundItems; 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(); std::map & getCorprusSpells(); void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); void removeCorprusSpell(const std::string& sourceId); float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/difficultyscaling.cpp000066400000000000000000000023131413061077700253740ustar00rootroot00000000000000#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] int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); difficultySetting = std::min(difficultySetting, 500); difficultySetting = std::max(difficultySetting, -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.47.0/apps/openmw/mwmechanics/difficultyscaling.hpp000066400000000000000000000004501413061077700254010ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/disease.hpp000066400000000000000000000061001413061077700233110ustar00rootroot00000000000000#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 (MWWorld::Ptr actor, MWWorld::Ptr carrier) { if (!carrier.getClass().isActor() || actor != getPlayer()) return; float fDiseaseXferChance = MWBase::Environment::get().getWorld()->getStore().get().find( "fDiseaseXferChance")->mValue.getFloat(); MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = it->first; 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); if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(it->first); 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.47.0/apps/openmw/mwmechanics/drawstate.hpp000066400000000000000000000005161413061077700236770ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_DRAWSTATE_H #define GAME_MWMECHANICS_DRAWSTATE_H namespace MWMechanics { /// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die! enum DrawState_ { DrawState_Nothing = 0, DrawState_Weapon = 1, DrawState_Spell = 2 }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/enchanting.cpp000066400000000000000000000351261413061077700240170ustar00rootroot00000000000000#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) , mWeaponType(-1) {} void Enchanting::setOldItem(const MWWorld::Ptr& oldItem) { mOldItemPtr=oldItem; mWeaponType = -1; mObjectType.clear(); if(!itemEmpty()) { mObjectType = mOldItemPtr.getTypeName(); if (mObjectType == typeid(ESM::Weapon).name()) 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) { if(getEnchantChance() <= (Misc::Rng::roll0to99())) 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 == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name())) { // 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 == typeid(ESM::Book).name()) { // 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::max(0.f, std::min(1.0f, Settings::Manager::getFloat("projectiles enchant multiplier", "Game"))); MWWorld::Ptr player = getPlayer(); int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints))); } } 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.47.0/apps/openmw/mwmechanics/enchanting.hpp000066400000000000000000000041141413061077700240150ustar00rootroot00000000000000#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; std::string 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.47.0/apps/openmw/mwmechanics/levelledlist.hpp000066400000000000000000000062311413061077700243710ustar00rootroot00000000000000#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::Seed& seed = Misc::Rng::getSeed()) { const std::vector& items = levItem->mList; const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); if (Misc::Rng::roll0to99(seed) < 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(), seed)]; // 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().getTypeName() != typeid(ESM::ItemLevList).name() && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) { return item; } else { if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) return getLevelledItem(ref.getPtr().get()->mBase, false, seed); else return getLevelledItem(ref.getPtr().get()->mBase, true, seed); } } } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/linkedeffects.cpp000066400000000000000000000062411413061077700245030ustar00rootroot00000000000000#include "linkedeffects.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" namespace MWMechanics { bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) { if (caster.isEmpty() || caster == target || !target.getClass().isActor()) return false; bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; if (!isHarmful || isUnreflectable) return false; float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); if (Misc::Rng::roll0to99() >= reflect) return false; const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation && !reflectStatic->mModel.empty()) animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); reflectedEffects.mList.emplace_back(effect); return true; } void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source) { if (caster.isEmpty() || caster == target) return; if (!target.getClass().isActor() || !caster.getClass().isActor()) return; // Make sure callers don't do something weird if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill) throw std::runtime_error("invalid absorb stat effect"); if (appliedEffect.mMagnitude == 0) return; std::vector absorbEffects; ActiveSpells::ActiveEffect absorbEffect = appliedEffect; absorbEffect.mMagnitude *= -1; absorbEffect.mEffectIndex = appliedEffect.mEffectIndex; absorbEffects.emplace_back(absorbEffect); // Morrowind negates reflected Absorb spells so the original caster won't be harmed. if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) { target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true, absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId()); return; } caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true, absorbEffects, source, target.getClass().getCreatureStats(target).getActorId()); } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/linkedeffects.hpp000066400000000000000000000016361413061077700245130ustar00rootroot00000000000000#ifndef MWMECHANICS_LINKEDEFFECTS_H #define MWMECHANICS_LINKEDEFFECTS_H #include namespace ESM { struct ActiveEffect; struct EffectList; struct ENAMstruct; struct MagicEffect; struct Spell; } namespace MWWorld { class Ptr; } namespace MWMechanics { // Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list. bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects); // Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster. void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/magiceffects.cpp000066400000000000000000000125321413061077700243150ustar00rootroot00000000000000#include "magiceffects.hpp" #include #include #include 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()); } } MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) { if (this==&effects) { MagicEffects temp (effects); *this += temp; return *this; } for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { Collection::iterator result = mCollection.find (iter->first); if (result!=mCollection.end()) result->second += iter->second; else mCollection.insert (*iter); } return *this; } 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 { // Don't need to save Modifiers, they are recalculated every frame anyway. for (Collection::const_iterator iter (begin()); iter!=end(); ++iter) { if (iter->second.getBase() != 0) { // Don't worry about mArg, never used by magic effect script instructions state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase())); } } } void MagicEffects::readState(const ESM::MagicEffects &state) { for (std::map::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it) { mCollection[EffectKey(it->first)].setBase(it->second); } } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/magiceffects.hpp000066400000000000000000000063671413061077700243330ustar00rootroot00000000000000#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; } // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display struct EffectSourceVisitor { virtual ~EffectSourceVisitor() { } virtual void visit (EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) = 0; }; /// \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); MagicEffects& operator+= (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.47.0/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp000066400000000000000000002250051413061077700256710ustar00rootroot00000000000000#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" 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; } } } namespace MWMechanics { void MechanicsManager::buildPlayer() { MWWorld::Ptr ptr = getPlayer(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); npcStats.setNeedRecalcDynamicStats(true); const ESM::NPC *player = ptr.get()->mBase; // reset creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(true); creatureStats.modifyMagicEffects(MagicEffects()); 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); 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::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) { auto& stats = actor.getClass().getCreatureStats (actor); auto& corprusSpells = stats.getCorprusSpells(); auto corprusIt = corprusSpells.find(sourceId); if (corprusIt != corprusSpells.end()) { for (int i = 0; i < ESM::Attribute::Length; ++i) { MWMechanics::AttributeValue attr = stats.getAttribute(i); attr.restore(corprusIt->second.mWorsenings[i]); actor.getClass().getCreatureStats(actor).setAttribute(i, attr); } } } 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(); // Update the equipped weapon icon MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end()) winMgr->unsetSelectedWeapon(); else winMgr->setSelectedWeapon(*weapon); // 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(); } 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); 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(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 addTemporaryDispositionChange) { 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(addTemporaryDispositionChange) x += MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used return effective_disposition; } 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.getTypeName() == typeid(ESM::Creature).name()) 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, float& tempChange, float& 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); 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; float iPerMinChance = floor(gmst.find("iPerMinChance")->mValue.getFloat()); float iPerMinChange = floor(gmst.find("iPerMinChange")->mValue.getFloat()); float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat(); float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat(); float x = 0; float y = 0; int roll = Misc::Rng::roll0to99(); 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); int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase(); int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase(); npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s))))); npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); } 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; int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase(); int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase(); npcStats.setAiSetting (CreatureStats::AI_Flee, std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s))))); npcStats.setAiSetting (CreatureStats::AI_Fight, std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s))))); } 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; } tempChange = type == PT_Intimidate ? x : int(x * fPerTempMult); float cappedDispositionChange = tempChange; if (currentDisposition + tempChange > 100.f) cappedDispositionChange = static_cast(100 - currentDisposition); if (currentDisposition + tempChange < 0.f) cappedDispositionChange = static_cast(-currentDisposition); permChange = floor(cappedDispositionChange / fPerTempMult); if (type == PT_Intimidate) { permChange = success ? -int(cappedDispositionChange/ fPerTempMult) : y; } } 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) && ptr.getCellRef().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; } 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(); std::map::const_iterator 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(Misc::StringUtils::lowerCase(globalVariable)) == 1) { isOwned = false; isFactionOwned = false; } if (!cellref.getOwner().empty()) victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); // A special case for evidence chest - we should not allow to take items even if it is technically permitted if (Misc::StringUtils::ciEqual(cellref.getRefId(), "stolen_goods")) return false; return (!isOwned && !isFactionOwned); } 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 (isAllowedToUse(ptr, item, victim)) return; 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(CreatureStats::AI_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().hasPackage(AiPackageTypeId::Pursue)) { 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(CreatureStats::AI_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; int observerFightRating = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_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(CreatureStats::AI_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().hasPackage(AiPackageTypeId::Pursue)) 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().hasPackage(AiPackageTypeId::Pursue)) { // 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) { int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified()); peaceful = (fight == 0); } if (!peaceful) startCombat(target, 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().hasPackage(AiPackageTypeId::Pursue); } 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 invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude(); if (invisibility > 0) return false; 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 x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; 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; return (Misc::Rng::roll0to99() >= 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 (Actors::PtrActorMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { if (iter->first.getClass().isClass(iter->first, "Guard")) { MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); if (aiSeq.getTypeId() == MWMechanics::AiPackageTypeId::Pursue) { aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); iter->first.getClass().getCreatureStats(iter->first).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::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::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); } std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); } std::list MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingIndices(actor); } std::map MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingByIndex(actor); } std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } std::list 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, true); int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_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(); if (actor == player->getPlayer()) { if (werewolf) { player->saveStats(); player->setWerewolfStats(); } else player->restoreStats(); } // 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(); if (werewolf) { windowManager->forceHide(MWGui::GW_Inventory); windowManager->forceHide(MWGui::GW_Magic); } else { 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::CreatureStats::AI_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); stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->mValue.getInteger()); } 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.47.0/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp000066400000000000000000000275111413061077700257000ustar00rootroot00000000000000#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) 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) override; ///< 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(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 addTemporaryDispositionChange = 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, float& tempChange, float& 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; /** * @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::list getActorsSidingWith(const MWWorld::Ptr& actor) override; std::list getActorsFollowing(const MWWorld::Ptr& actor) override; std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override; std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; std::list getActorsFighting(const MWWorld::Ptr& actor) override; std::list 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; void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) 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.47.0/apps/openmw/mwmechanics/movement.hpp000066400000000000000000000021071413061077700235310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/npcstats.cpp000066400000000000000000000410341413061077700235330ustar00rootroot00000000000000#include "npcstats.hpp" #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::min(255, std::max(0, reputation)); } 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.47.0/apps/openmw/mwmechanics/npcstats.hpp000066400000000000000000000113441413061077700235410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/objects.cpp000066400000000000000000000076001413061077700233260ustar00rootroot00000000000000#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 { Objects::Objects() { } Objects::~Objects() { for(auto& object : mObjects) { delete object.second; object.second = nullptr; } } void Objects::addObject(const MWWorld::Ptr& ptr) { removeObject(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if(anim) mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); } void Objects::removeObject(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) { delete iter->second; mObjects.erase(iter); } } void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { PtrControllerMap::iterator iter = mObjects.find(old); if(iter != mObjects.end()) { CharacterController *ctrl = iter->second; mObjects.erase(iter); ctrl->updatePtr(ptr); mObjects.insert(std::make_pair(ptr, ctrl)); } } void Objects::dropObjects (const MWWorld::CellStore *cellStore) { PtrControllerMap::iterator iter = mObjects.begin(); while(iter != mObjects.end()) { if(iter->first.getCell()==cellStore) { delete iter->second; mObjects.erase(iter++); } else ++iter; } } void Objects::update(float duration, bool paused) { if(!paused) { for(auto& object : mObjects) object.second->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(auto& object : mObjects) { if (object.first.getTypeName() != typeid(ESM::Container).name()) continue; if (object.second->isAnimPlaying("containeropen")) { object.second->update(duration); MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(object.first); } } } } bool Objects::onOpen(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) return iter->second->onOpen(); return true; } void Objects::onClose(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) iter->second->onClose(); } bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.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) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) iter->second->skipAnim(); } void Objects::persistAnimationStates() { for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) iter->second->persistAnimationState(); } void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) { if ((position - iter->first.getRefData().getPosition().asVec3()).length2() <= radius*radius) out.push_back(iter->first); } } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/objects.hpp000066400000000000000000000027511413061077700233350ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTIVATORS_H #define GAME_MWMECHANICS_ACTIVATORS_H #include #include #include namespace osg { class Vec3f; } namespace MWWorld { class Ptr; class CellStore; } namespace MWMechanics { class CharacterController; class Objects { typedef std::map PtrControllerMap; PtrControllerMap mObjects; public: Objects(); ~Objects(); 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); std::size_t size() const { return mObjects.size(); } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/obstacle.cpp000066400000000000000000000137351413061077700234770ustar00rootroot00000000000000#include "obstacle.hpp" #include #include "../mwworld/class.hpp" #include "../mwworld/cellstore.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 } 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(); return; } if (mWalkState != WalkState::Evade) { 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.47.0/apps/openmw/mwmechanics/obstacle.hpp000066400000000000000000000032001413061077700234660ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_OBSTACLE_H #define OPENMW_MECHANICS_OBSTACLE_H #include namespace MWWorld { class Ptr; } 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); 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; // 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.47.0/apps/openmw/mwmechanics/pathfinding.cpp000066400000000000000000000514741413061077700242000ustar00rootroot00000000000000#include "pathfinding.hpp" #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 osg::Vec3f mHalfExtents; const DetourNavigator::Flags mFlags; bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const { const auto position = mNavigator->raycast(mHalfExtents, 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) { if (mPath.empty()) return; while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); if (shortenIfAlmostStraight) { const IsValidShortcut isValidShortcut { MWBase::Environment::get().getWorld()->getNavigator(), halfExtents, flags }; 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) { 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { 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, halfExtents, flags, areaCosts, 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { mPath.clear(); mCell = cell; DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound; if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); } if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, 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 = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out); 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 osg::Vec3f& halfExtents, 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 auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, areaCosts, 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { 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, halfExtents, flags, areaCosts); const auto end = startPoint + startToEnd * maxDistance / distance; buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts); } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/pathfinding.hpp000066400000000000000000000203061413061077700241730ustar00rootroot00000000000000#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 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); 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, 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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents, 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(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 osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/pathgrid.cpp000066400000000000000000000277161413061077700235110ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/pathgrid.hpp000066400000000000000000000050601413061077700235020ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/pickpocket.cpp000066400000000000000000000043601413061077700240310ustar00rootroot00000000000000#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(); int roll = Misc::Rng::roll0to99(); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); } else { t = std::min(float(iPickMaxChance), t); return (roll > int(t)); } } bool Pickpocket::pick(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.47.0/apps/openmw/mwmechanics/pickpocket.hpp000066400000000000000000000012571413061077700240400ustar00rootroot00000000000000#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 (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.47.0/apps/openmw/mwmechanics/recharge.cpp000066400000000000000000000064461413061077700234640ustar00rootroot00000000000000#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(); int roll = Misc::Rng::roll0to99(); 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.47.0/apps/openmw/mwmechanics/recharge.hpp000066400000000000000000000004531413061077700234610ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/repair.cpp000066400000000000000000000073121413061077700231570ustar00rootroot00000000000000#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; int roll = Misc::Rng::roll0to99(); 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.47.0/apps/openmw/mwmechanics/repair.hpp000066400000000000000000000006141413061077700231620ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/security.cpp000066400000000000000000000101671413061077700235460ustar00rootroot00000000000000#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 { if (Misc::Rng::roll0to99() <= 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 { if (Misc::Rng::roll0to99() <= 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.47.0/apps/openmw/mwmechanics/security.hpp000066400000000000000000000012741413061077700235520ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/spellabsorption.cpp000066400000000000000000000067561413061077700251300ustar00rootroot00000000000000#include "spellabsorption.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "creaturestats.hpp" #include "spellutil.hpp" namespace MWMechanics { class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor { public: float mProbability{0.f}; GetAbsorptionProbability() = default; void visit (MWMechanics::EffectKey key, int /*effectIndex*/, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float magnitude, float /*remainingTime*/, float /*totalTime*/) override { if (key.mId == ESM::MagicEffect::SpellAbsorption) { if (mProbability == 0.f) mProbability = magnitude / 100; else { // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. // Real absorption probability will be the (1 - total fail chance) in this case. float failProbability = 1.f - mProbability; failProbability *= 1.f - magnitude / 100; mProbability = 1.f - failProbability; } } } }; int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { if(target.isEmpty() || caster == target || !target.getClass().isActor()) return 0; CreatureStats& stats = target.getClass().getCreatureStats(target); if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) return 0; GetAbsorptionProbability check; stats.getActiveSpells().visitEffectSources(check); stats.getSpells().visitEffectSources(check); if (target.getClass().hasInventoryStore(target)) target.getClass().getInventoryStore(target).visitEffectSources(check); return check.mProbability * 100; } void absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { CreatureStats& stats = target.getClass().getCreatureStats(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()) animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string()); const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; if (spell) { spellCost = spell->mData.mCost; } else { const ESM::Enchantment* enchantment = esmStore.get().search(spellId); if (enchantment) spellCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), caster); } // Magicka is increased by the cost of the spell DynamicStat magicka = stats.getMagicka(); magicka.setCurrent(magicka.getCurrent() + spellCost); stats.setMagicka(magicka); } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/spellabsorption.hpp000066400000000000000000000007371413061077700251260ustar00rootroot00000000000000#ifndef MWMECHANICS_SPELLABSORPTION_H #define MWMECHANICS_SPELLABSORPTION_H #include namespace MWWorld { class Ptr; } namespace MWMechanics { void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); // Calculate the chance to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/spellcasting.cpp000066400000000000000000001051511413061077700243650ustar00rootroot00000000000000#include "spellcasting.hpp" #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 "linkedeffects.hpp" #include "spellabsorption.hpp" #include "spellresistance.hpp" #include "spellutil.hpp" #include "summoning.hpp" #include "tickableeffects.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); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, 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; for (const ESM::ENAMstruct& effect : effects.mList) { if (effect.mRange == range) { found = true; break; } } 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(); if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); return; } } ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // Otherwise, they'd only apply after the whole spell was added. MagicEffects targetEffects; if (targetIsActor) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; if (targetIsActor) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false // throughout the iteration of this spell's // effects, we display a "can't re-cast" message int absorbChance = getAbsorbChance(caster, target); int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) { if (effectIt->mRange != range) continue; const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); // Re-casting a bound equipment effect has no effect if the spell is still active if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) { if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); continue; } canCastAnEffect = true; // Try absorbing the effect if(absorbChance && Misc::Rng::roll0to99() < absorbChance) { absorbSpell(mId, caster, target); continue; } if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; // caster needs to be an actor for linked effects (e.g. Absorb) if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (caster.isEmpty() || !caster.getClass().isActor())) continue; // 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); // Reflect harmful effects if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) continue; // Try resisting. float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); if (magnitudeMult == 0) { // Fully resisted, show message if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } else { float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1); magnitude *= magnitudeMult; if (!target.getClass().isActor()) { // non-actor objects have no list of active magic effects, so have to apply instantly if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) continue; } else // target.getClass().isActor() == true { ActiveSpells::ActiveEffect effect; effect.mEffectId = effectIt->mEffectID; effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; effect.mMagnitude = magnitude; effect.mTimeLeft = 0.f; effect.mEffectIndex = currentEffectIndex; // Avoid applying absorb effects if the caster is the target // We still need the spell to be added if (caster == target && effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) { effect.mMagnitude = 0; } // Avoid applying harmful effects to the player in god mode if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) { effect.mMagnitude = 0; } bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && 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); } bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; if (!appliedOnce) effect.mDuration = std::max(1.f, effect.mDuration); if (effect.mDuration == 0) { // We still should add effect to list to allow GetSpellEffects to detect this spell appliedLastingEffects.push_back(effect); // duration 0 means apply full magnitude instantly bool wasDead = target.getClass().getCreatureStats(target).isDead(); effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude); bool isDead = target.getClass().getCreatureStats(target).isDead(); if (!wasDead && isDead) MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); } else { effect.mTimeLeft = effect.mDuration; targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); // add to list of active effects, to apply in next frame appliedLastingEffects.push_back(effect); // Unequip all items, if a spell with the ExtraSpell effect was casted if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target)) { MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target); store.unequipAll(target); } // Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc()) || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) { MWMechanics::AiFollow package(caster, true); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); } // For absorb effects, also apply the effect to the caster - but with a negative // magnitude, since we're transferring stats from the target to the caster if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) absorbStat(*effectIt, effect, caster, target, reflected, mSourceName); } } // Re-casting a summon effect will remove the creature from previous castings of that effect. if (isSummoningEffect(effectIt->mEffectID) && targetIsActor) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); auto findCreature = targetStats.getSummonedCreatureMap().find(key); if (findCreature != targetStats.getSummonedCreatureMap().end()) { MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second); targetStats.getSummonedCreatureMap().erase(findCreature); } } if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { 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; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); if (anim && !castStatic->mModel.empty()) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); } } } if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); if (!target.isEmpty()) { if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); if (!appliedLastingEffects.empty()) { int casterActorId = -1; if (!caster.isEmpty() && caster.getClass().isActor()) casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName, casterActorId); } } } bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) { short effectId = effect.mId; if (target.getClass().canLock(target)) { if (effectId == ESM::MagicEffect::Lock) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *magiceffect = store.get().find(effectId); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation) animation->addSpellCastGlow(magiceffect); 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(static_cast(magnitude)); } return true; } else if (effectId == ESM::MagicEffect::Open) { if (!caster.isEmpty()) { MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); // Use the player instead of the caster for vanilla crime compatibility } const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *magiceffect = store.get().find(effectId); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation) animation->addSpellCastGlow(magiceffect); 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); } return true; } } else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) { target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); return true; } else if (target.getClass().isActor() && target == getPlayer()) { MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled(); if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention) { if (!teleportingEnabled) { if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); return true; } std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker); anim->removeEffect(effectId); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_end"); if (fx) anim->addEffect("meshes\\" + fx->mModel, -1); return true; } else if (effectId == ESM::MagicEffect::Mark) { if (teleportingEnabled) { MWBase::Environment::get().getWorld()->getPlayer().markPosition( target.getCell(), target.getRefData().getPosition()); } else if (caster == getPlayer()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); } return true; } else if (effectId == ESM::MagicEffect::Recall) { if (!teleportingEnabled) { if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); return true; } MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell) { MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); action.execute(target); anim->removeEffect(effectId); } return true; } } return false; } 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, 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); mStack = false; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; if (item.getTypeName() == typeid(ESM::Weapon).name()) { 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); } 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; mStack = true; inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); return true; } bool CastSpell::cast(const ESM::Spell* spell) { mSourceName = spell->mName; mId = spell->mId; mStack = false; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); 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) { // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->mValue.getFloat(); static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->mValue.getFloat(); DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); bool fail = false; // Check success float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false); if (Misc::Rng::roll0to99() >= 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; mStack = true; 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(); int roll = Misc::Rng::roll0to99(); 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; 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(), "meshes\\" + castStatic->mModel) != addedEffects.end()) continue; MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", effect->mParticle); } else { // If the caster has no animation, add the effect directly to the effectManager // We should scale it manually osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f / Constants::UnitsPerFoot); float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f }); float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; addedEffects.push_back("meshes\\" + castStatic->mModel); 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); } } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/spellcasting.hpp000066400000000000000000000051641413061077700243750ustar00rootroot00000000000000#ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H #include #include "../mwworld/ptr.hpp" namespace ESM { struct Spell; struct Ingredient; struct Potion; struct EffectList; } 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: bool mStack{false}; 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.) 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, 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 reflected=false, bool exploded=false); /// @note \a caster can be any type of object, or even an empty object. /// @return was the target suitable for the effect? bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/spelllist.cpp000066400000000000000000000116531413061077700237130ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/spelllist.hpp000066400000000000000000000044571413061077700237240ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_SPELLLIST_H #define GAME_MWMECHANICS_SPELLLIST_H #include #include #include #include #include #include namespace ESM { struct SpellState; } namespace MWMechanics { struct SpellParams { std::map mEffectRands; // std::set mPurgedEffects; // indices of purged effects }; 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.47.0/apps/openmw/mwmechanics/spellpriority.cpp000066400000000000000000000624721413061077700246260ustar00rootroot00000000000000#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->first); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) { int effectId = effectIt->mEffectId; if (effectFilter != -1 && effectId != effectFilter) continue; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); if (effectIt->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->first != spellId) continue; const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) { if (effectIt->mDuration > duration) duration = effectIt->mDuration; } } return duration; } } 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.getTypeName() != typeid(ESM::Potion).name()) 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) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); 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) && stats.getActiveSpells().isSpellActive(spell->mId)) return 0.f; if ( ((types & Touch) || (types & Target)) && enemy.getClass().getCreatureStats(enemy).getActiveSpells().isSpellActive(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; // 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; } // 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.47.0/apps/openmw/mwmechanics/spellpriority.hpp000066400000000000000000000022221413061077700246160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/spellresistance.cpp000066400000000000000000000067501413061077700251020ustar00rootroot00000000000000#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; float roll = Misc::Rng::rollClosedProbability() * 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.47.0/apps/openmw/mwmechanics/spellresistance.hpp000066400000000000000000000032371413061077700251040ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/spells.cpp000066400000000000000000000410121413061077700231720ustar00rootroot00000000000000#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() : mSpellsChanged(false) { } Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) { 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)), mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)), mSourcedEffects(std::move(spells.mSourcedEffects)) { if (mSpellList) mSpellList->updateListener(&spells, this); } std::map::const_iterator Spells::begin() const { return mSpells.begin(); } std::map::const_iterator Spells::end() const { return mSpells.end(); } void Spells::rebuildEffects() const { mEffects = MagicEffects(); mSourcedEffects.clear(); for (const auto& iter : mSpells) { const ESM::Spell *spell = iter.first; if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) { int i=0; for (const auto& effect : spell->mEffects.mList) { if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) { ++i; continue; // effect was purged } float random = 1.f; if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) random = iter.second.mEffectRands.at(i); float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; mEffects.add (effect, magnitude); mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); ++i; } } } } bool Spells::hasSpell(const std::string &spell) const { return hasSpell(SpellList::getSpell(spell)); } bool Spells::hasSpell(const ESM::Spell *spell) const { return mSpells.find(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 (mSpells.find (spell)==mSpells.end()) { std::map random; // Determine the random magnitudes (unless this is a castable spell, in which case // they will be determined when the spell is cast) if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) { for (unsigned int i=0; imEffects.mList.size();++i) { if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) { int delta = spell->mEffects.mList[i].mMagnMax - spell->mEffects.mList[i].mMagnMin; random[i] = Misc::Rng::rollDice(delta + 1) / static_cast(delta); } } } SpellParams params; params.mEffectRands = random; mSpells.emplace(spell, params); mSpellsChanged = true; } } 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 = mSpells.find(spell); if(it != mSpells.end()) { mSpells.erase(it); mSpellsChanged = true; } } MagicEffects Spells::getMagicEffects() const { if (mSpellsChanged) { rebuildEffects(); mSpellsChanged = false; } return mEffects; } void Spells::removeAllSpells() { mSpells.clear(); mSpellsChanged = true; } 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::isSpellActive(const std::string &id) const { if (id.empty()) return false; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell && hasSpell(spell)) { auto type = spell->mData.mType; return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse); } return false; } bool Spells::hasDisease(const ESM::Spell::SpellType type) const { for (const auto& iter : mSpells) { const ESM::Spell *spell = iter.first; if (spell->mData.mType == type) return true; } return false; } bool Spells::hasCommonDisease() const { return hasDisease(ESM::Spell::ST_Disease); } bool Spells::hasBlightDisease() const { return hasDisease(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->first; if (filter(spell)) { mSpells.erase(iter++); purged.push_back(spell->mId); mSpellsChanged = true; } 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; }); } void Spells::removeEffects(const std::string &id) { if (isSpellActive(id)) { for (auto& spell : mSpells) { if (spell.first == SpellList::getSpell(id)) { for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) { spell.second.mPurgedEffects.insert(i); } } } mSpellsChanged = true; } } void Spells::visitEffectSources(EffectSourceVisitor &visitor) const { if (mSpellsChanged) { rebuildEffects(); mSpellsChanged = false; } for (const auto& it : mSourcedEffects) { const ESM::Spell * spell = it.first; for (const auto& effectIt : it.second) { // FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); } } } bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (const auto& effectIt : spell->mEffects.mList) { if (effectIt.mEffectID == ESM::MagicEffect::Corprus) { return true; } } return false; } void Spells::purgeEffect(int effectId) { for (auto& spellIt : mSpells) { int i = 0; for (auto& effectIt : spellIt.first->mEffects.mList) { if (effectIt.mEffectID == effectId) { spellIt.second.mPurgedEffects.insert(i); mSpellsChanged = true; } ++i; } } } void Spells::purgeEffect(int effectId, const std::string & sourceId) { // Effect source may be not a spell const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(sourceId); if (spell == nullptr) return; auto spellIt = mSpells.find(spell); if (spellIt == mSpells.end()) return; int index = 0; for (auto& effectIt : spellIt->first->mEffects.mList) { if (effectIt.mEffectID == effectId) { spellIt->second.mPurgedEffects.insert(index); mSpellsChanged = true; } ++index; } } bool Spells::canUsePower(const ESM::Spell* spell) const { const auto it = mUsedPowers.find(spell); return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) { mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { const auto& baseSpells = mSpellList->getSpells(); for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Discard spells that are no longer available due to changed content files const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (spell) { mSpells[spell].mEffectRands = it->second.mEffectRands; mSpells[spell].mPurgedEffects = it->second.mPurgedEffects; if (it->first == state.mSelectedSpell) mSelectedSpell = it->first; } } // 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[spell] = MWWorld::TimeStamp(it->second); } for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) { const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (!spell) continue; CorprusStats stats; int worsening = state.mCorprusSpells.at(it->first).mWorsenings; for (int i=0; imEffects.mList) { if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) stats.mWorsenings[effect.mAttribute] = worsening; } stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); creatureStats->addCorprusSpell(it->first, stats); } mSpellsChanged = true; // 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& it : mSpells) { // Don't save spells and powers stored in the base record if((it.first->mData.mType != ESM::Spell::ST_Spell && it.first->mData.mType != ESM::Spell::ST_Power) || std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) { ESM::SpellState::SpellParams params; params.mEffectRands = it.second.mEffectRands; params.mPurgedEffects = it.second.mPurgedEffects; state.mSpells.emplace(it.first->mId, params); } } 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.47.0/apps/openmw/mwmechanics/spells.hpp000066400000000000000000000076231413061077700232110ustar00rootroot00000000000000#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::map 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::map mUsedPowers; mutable bool mSpellsChanged; mutable MagicEffects mEffects; mutable std::map mSourcedEffects; void rebuildEffects() const; bool hasDisease(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: using TIterator = std::map::const_iterator; Spells(); Spells(const Spells&); Spells(Spells&& spells); ~Spells(); static bool hasCorprusEffect(const ESM::Spell *spell); void purgeEffect(int effectId); void purgeEffect(int effectId, const std::string & sourceId); bool canUsePower (const ESM::Spell* spell) const; void usePower (const ESM::Spell* spell); void purgeCommonDisease(); void purgeBlightDisease(); void purgeCorprusDisease(); void purgeCurses(); TIterator begin() const; TIterator 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). MagicEffects getMagicEffects() const; ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. 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 isSpellActive(const std::string& id) const; ///< Are we under the effects of the given spell ID? bool hasCommonDisease() const; bool hasBlightDisease() const; void removeEffects(const std::string& id); void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) 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.47.0/apps/openmw/mwmechanics/spellutil.cpp000066400000000000000000000206631413061077700237160ustar00rootroot00000000000000#include "spellutil.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.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 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(); 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; return x * fEffectCostMult; } 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 - spell->mData.mCost + 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 && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) 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(); return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); } 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); } bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) { switch (effectId) { case ESM::MagicEffect::Levitate: { if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); return false; } break; } case ESM::MagicEffect::Soultrap: { if (!target.getClass().isNpc() // no messagebox for NPCs && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); return true; // must still apply to get visual effect and have target regard it as attack } break; } case ESM::MagicEffect::WaterWalking: { if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) return false; MWBase::World *world = MWBase::Environment::get().getWorld(); if (!world->isWaterWalkingCastableOnTarget(target)) { if (castByPlayer && caster == target) MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); return false; } break; } } return true; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/spellutil.hpp000066400000000000000000000037371413061077700237260ustar00rootroot00000000000000#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); float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); 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); /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/stat.cpp000066400000000000000000000200401413061077700226410ustar00rootroot00000000000000#include "stat.hpp" #include namespace MWMechanics { template Stat::Stat() : mBase (0), mModified (0), mCurrentModified (0) {} template Stat::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {} template Stat::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {} template const T& Stat::getBase() const { return mBase; } template T Stat::getModified(bool capped) const { if(!capped) return mModified; return std::max(static_cast(0), mModified); } template T Stat::getCurrentModified() const { return mCurrentModified; } template T Stat::getModifier() const { return mModified-mBase; } template T Stat::getCurrentModifier() const { return mCurrentModified - mModified; } template void Stat::set (const T& value) { T diff = value - mBase; mBase = mModified = value; mCurrentModified += diff; } template void Stat::setBase (const T& value) { T diff = value - mBase; mBase = value; mModified += diff; mCurrentModified += diff; } template void Stat::setModified (T value, const T& min, const T& max) { T diff = value - mModified; if (mBase+diffmax) { value = max + (mModified - mBase); diff = value - mModified; } mModified = value; mBase += diff; mCurrentModified += diff; } template void Stat::setCurrentModified(T value) { mCurrentModified = value; } template void Stat::setModifier (const T& modifier) { mModified = mBase + modifier; } template void Stat::setCurrentModifier(const T& modifier) { mCurrentModified = mModified + modifier; } template void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; state.mMod = mCurrentModified; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; mModified = state.mBase; mCurrentModified = state.mMod; } template DynamicStat::DynamicStat() : mStatic (0), mCurrent (0) {} template DynamicStat::DynamicStat(T base) : mStatic (base), 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 const T& DynamicStat::getBase() const { return mStatic.getBase(); } template T DynamicStat::getModified() const { return mStatic.getModified(); } template T DynamicStat::getCurrentModified() const { return mStatic.getCurrentModified(); } template const T& DynamicStat::getCurrent() const { return mCurrent; } template void DynamicStat::set (const T& value) { mStatic.set (value); mCurrent = value; } template void DynamicStat::setBase (const T& value) { mStatic.setBase (value); if (mCurrent>getModified()) mCurrent = getModified(); } template void DynamicStat::setModified (T value, const T& min, const T& max) { mStatic.setModified (value, min, max); if (mCurrent>getModified()) mCurrent = getModified(); } template void DynamicStat::setCurrentModified(T value) { mStatic.setCurrentModified(value); } 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 void DynamicStat::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero) { T diff = modifier - mStatic.getModifier(); mStatic.setModifier (modifier); setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); } template void DynamicStat::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero) { T diff = modifier - mStatic.getCurrentModifier(); mStatic.setCurrentModifier(modifier); // The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active). setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0)); } 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) { mBase = base; } void AttributeValue::setModifier(float mod) { mModifier = mod; } void AttributeValue::damage(float damage) { float threshold = mBase + mModifier; if (mDamage + damage > threshold) mDamage = threshold; else 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.47.0/apps/openmw/mwmechanics/stat.hpp000066400000000000000000000133321413061077700226540ustar00rootroot00000000000000#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 mModified; T mCurrentModified; public: typedef T Type; Stat(); Stat(T base); Stat(T base, T modified); const T& getBase() const; T getModified(bool capped = true) const; T getCurrentModified() const; T getModifier() const; T getCurrentModifier() const; /// Set base and modified to \a value. void set (const T& value); /// Set base and adjust modified accordingly. void setBase (const T& value); /// Set modified value and adjust base accordingly. void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); /// Set "current modified," used for drain and fortify. Unlike the regular modifier /// this just adds and subtracts from the current value without changing the maximum. void setCurrentModified(T value); void setModifier (const T& modifier); void setCurrentModifier (const T& 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.getModified()==right.getModified(); } 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; T getModified() const; T getCurrentModified() const; const T& getCurrent() const; /// Set base, modified and current to \a value. void set (const T& value); /// Set base and adjust modified accordingly. void setBase (const T& value); /// Set modified value and adjust base accordingly. void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); /// Set "current modified," used for drain and fortify. Unlike the regular modifier /// this just adds and subtracts from the current value without changing the maximum. void setCurrentModified(T value); void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false); void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false); 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.getModified()==right.getModified() && 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); void setModifier(float mod); // Maximum attribute damage is limited to the modified value. // Note: I think MW applies damage directly to mModified, since you can also // "restore" drained attributes. We need to rewrite the magic effect system to support this. 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.47.0/apps/openmw/mwmechanics/steering.cpp000066400000000000000000000025221413061077700235130ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/steering.hpp000066400000000000000000000015641413061077700235250ustar00rootroot00000000000000#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) { const float baseAngluarVelocity = 10; const float baseSpeed = 200; return baseAngluarVelocity * 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.47.0/apps/openmw/mwmechanics/summoning.cpp000066400000000000000000000176231413061077700237170ustar00rootroot00000000000000#include "summoning.hpp" #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(); } UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) : mActor(actor) { } void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) { if (isSummoningEffect(key.mId) && magnitude > 0) { mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex)); } } void UpdateSummonedCreatures::process(bool cleanup) { MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); std::map& creatureMap = creatureStats.getSummonedCreatureMap(); for (std::set::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) { bool found = creatureMap.find(*it) != creatureMap.end(); if (!found) { std::string creatureID = getSummonedCreature(it->mEffectId); if (!creatureID.empty()) { int creatureActorId = -1; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->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(mActor); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); if (anim) { const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_Start"); if (fx) anim->addEffect("meshes\\" + fx->mModel, -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 } creatureMap.emplace(*it, creatureActorId); } } } // Update summon effects for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) { bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); if (!found) { // Effect has ended MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); creatureMap.erase(it++); continue; } ++it; } std::vector graveyard = creatureStats.getSummonedCreatureGraveyard(); creatureStats.getSummonedCreatureGraveyard().clear(); for (const int creature : graveyard) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, creature); if (!cleanup) return; for (std::map::iterator 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 purgeSummonEffect(mActor, *it); creatureMap.erase(it++); } else ++it; } } void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex); creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId); if (summoner.getClass().hasInventoryStore(summoner)) summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex); MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/summoning.hpp000066400000000000000000000021421413061077700237120ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_SUMMONING_H #define OPENMW_MECHANICS_SUMMONING_H #include #include "../mwworld/ptr.hpp" #include #include "magiceffects.hpp" namespace MWMechanics { class CreatureStats; bool isSummoningEffect(int effectId); std::string getSummonedCreature(int effectId); void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); struct UpdateSummonedCreatures : public EffectSourceVisitor { UpdateSummonedCreatures(const MWWorld::Ptr& actor); virtual ~UpdateSummonedCreatures() = default; void visit (MWMechanics::EffectKey key, int effectIndex, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) override; /// To call after all effect sources have been visited void process(bool cleanup); private: MWWorld::Ptr mActor; std::set mActiveEffects; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/tickableeffects.cpp000066400000000000000000000176311413061077700250200ustar00rootroot00000000000000#include "tickableeffects.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "actorutil.hpp" #include "npcstats.hpp" namespace MWMechanics { void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false) { DynamicStat stat = creatureStats.getDynamic(index); stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero); creatureStats.setDynamic(index, stat); } bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate) { if (!ptr.getClass().hasInventoryStore(ptr)) return false; 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 != getPlayer()) inv.autoEquip(ptr); else inv.unequipItem(*item, ptr); } return true; } return false; } bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) { if (magnitude == 0.f) return false; bool receivedMagicDamage = false; bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); switch (effectKey.mId) { case ESM::MagicEffect::DamageAttribute: { if (godmode) break; AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); attr.damage(magnitude); creatureStats.setAttribute(effectKey.mArg, attr); break; } case ESM::MagicEffect::RestoreAttribute: { AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); attr.restore(magnitude); creatureStats.setAttribute(effectKey.mArg, attr); break; } case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); break; case ESM::MagicEffect::DamageHealth: if (godmode) break; receivedMagicDamage = true; adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); break; case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageFatigue: { if (godmode) break; int index = effectKey.mId-ESM::MagicEffect::DamageHealth; static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); break; } case ESM::MagicEffect::AbsorbHealth: if (!godmode || magnitude <= 0) { if (magnitude > 0.f) receivedMagicDamage = true; adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); } break; case ESM::MagicEffect::AbsorbMagicka: case ESM::MagicEffect::AbsorbFatigue: if (!godmode || magnitude <= 0) adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); break; case ESM::MagicEffect::DisintegrateArmor: { 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(actor, priority, magnitude)) break; } break; } case ESM::MagicEffect::DisintegrateWeapon: if (!godmode) disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); break; case ESM::MagicEffect::SunDamage: { // isInCell shouldn't be needed, but updateActor called during game start if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode) break; float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicSunBlockedMult")->mValue.getFloat(); int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); if (weather > 1) damageScale *= fMagicSunBlockedMult; adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); if (magnitude * damageScale > 0.f) receivedMagicDamage = true; break; } case ESM::MagicEffect::FireDamage: case ESM::MagicEffect::ShockDamage: case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::Poison: { if (godmode) break; adjustDynamicStat(creatureStats, 0, -magnitude); receivedMagicDamage = true; break; } case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::RestoreSkill: { if (!actor.getClass().isNpc()) break; if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill) break; NpcStats &npcStats = actor.getClass().getNpcStats(actor); SkillValue& skill = npcStats.getSkill(effectKey.mArg); if (effectKey.mId == ESM::MagicEffect::RestoreSkill) skill.restore(magnitude); else skill.damage(magnitude); break; } default: return false; } if (receivedMagicDamage && actor == getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); return true; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/tickableeffects.hpp000066400000000000000000000011501413061077700250120ustar00rootroot00000000000000#ifndef MWMECHANICS_TICKABLEEFFECTS_H #define MWMECHANICS_TICKABLEEFFECTS_H namespace MWWorld { class Ptr; } namespace MWMechanics { class CreatureStats; struct EffectKey; /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed /// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation. /// @return Was the effect a tickable effect with a magnitude? bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude); } #endif openmw-openmw-0.47.0/apps/openmw/mwmechanics/trading.cpp000066400000000000000000000064571413061077700233360ustar00rootroot00000000000000#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.getTypeName() != typeid(ESM::NPC).name() ) { 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); int roll = Misc::Rng::rollDice(100) + 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 = floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); } else if ( buying && (finalPrice < initialMerchantOffer) ) { skillGain = floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); return true; } } openmw-openmw-0.47.0/apps/openmw/mwmechanics/trading.hpp000066400000000000000000000005011413061077700233230ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/typedaipackage.hpp000066400000000000000000000013131413061077700246500ustar00rootroot00000000000000#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(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.47.0/apps/openmw/mwmechanics/weaponpriority.cpp000066400000000000000000000172341413061077700247740ustar00rootroot00000000000000#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.getTypeName() != typeid(ESM::Weapon).name()) 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. float chance = getHitChance(actor, enemy, value) / 100.f; rating *= std::min(1.f, std::max(0.01f, chance)); 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.47.0/apps/openmw/mwmechanics/weaponpriority.hpp000066400000000000000000000012251413061077700247720ustar00rootroot00000000000000#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.47.0/apps/openmw/mwmechanics/weapontype.cpp000066400000000000000000000033401413061077700240650ustar00rootroot00000000000000#include "weapontype.hpp" #include "../mwworld/class.hpp" namespace MWMechanics { MWWorld::ContainerStoreIterator getActiveWeapon(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 { const std::string &type = weapon->getTypeName(); if(type == typeid(ESM::Weapon).name()) { const MWWorld::LiveCellRef *ref = weapon->get(); *weaptype = ref->mBase->mData.mType; } else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) *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.47.0/apps/openmw/mwmechanics/weapontype.hpp000066400000000000000000000242221413061077700240740ustar00rootroot00000000000000#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(MWWorld::Ptr actor, int *weaptype); const ESM::WeaponType* getWeaponType(const int weaponType); } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/000077500000000000000000000000001413061077700207165ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwphysics/actor.cpp000066400000000000000000000200551413061077700225340ustar00rootroot00000000000000#include "actor.hpp" #include #include #include #include #include #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" #include namespace MWPhysics { Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , 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() && mHalfExtents.length2() == 0.f) { if (shape->mCollisionShape) { btTransform transform; transform.setIdentity(); btVector3 min; btVector3 max; shape->mCollisionShape->getAabb(transform, min, max); mHalfExtents.x() = (max[0] - min[0])/2.f; mHalfExtents.y() = (max[1] - min[1])/2.f; mHalfExtents.z() = (max[2] - min[2])/2.f; mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); } if (mHalfExtents.length2() == 0.f) Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; 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); updateScale(); if(!mRotationallyInvariant) updateRotation(); updatePosition(); addCollisionMask(getCollisionMask()); updateCollisionObjectPosition(); } Actor::~Actor() { mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) { mInternalCollisionMode.store(collision, std::memory_order_release); } 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; mSkipCollisions = true; mSkipSimulation = true; } void Actor::setSimulationPosition(const osg::Vec3f& position) { if (!std::exchange(mSkipSimulation, false)) mSimulationPosition = position; } osg::Vec3f Actor::getSimulationPosition() const { return mSimulationPosition; } 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); mWorldPositionChanged = false; } 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); applyOffsetChange(); bool hasChanged = mPosition != position || mWorldPositionChanged; mPreviousPosition = mPosition; mPosition = position; return hasChanged; } void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions) { std::scoped_lock lock(mPositionMutex); mPositionOffset += offset; mSkipCollisions = mSkipCollisions || ignoreCollisions; } void Actor::applyOffsetChange() { if (mPositionOffset.length() == 0) return; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; mSimulationPosition += mPositionOffset; mPositionOffset = osg::Vec3f(); mWorldPositionChanged = true; } osg::Vec3f Actor::getPosition() const { return mPosition; } osg::Vec3f Actor::getPreviousPosition() const { return mPreviousPosition; } void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } 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; scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; } osg::Vec3f Actor::getHalfExtents() const { std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mScale); } osg::Vec3f Actor::getOriginalHalfExtents() const { return mHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mRenderingScale); } void Actor::setInertialForce(const osg::Vec3f &force) { mForce = force; } void Actor::setOnGround(bool grounded) { mOnGround.store(grounded, std::memory_order_release); } void Actor::setOnSlope(bool slope) { mOnSlope.store(slope, std::memory_order_release); } bool Actor::isWalkingOnWater() const { return mWalkingOnWater.load(std::memory_order_acquire); } void Actor::setWalkingOnWater(bool walkingOnWater) { mWalkingOnWater.store(walkingOnWater, std::memory_order_release); } 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::skipCollisions() { return std::exchange(mSkipCollisions, false); } void Actor::setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } osg::Vec3f Actor::velocity() { return std::exchange(mVelocity, osg::Vec3f()); } } openmw-openmw-0.47.0/apps/openmw/mwphysics/actor.hpp000066400000000000000000000154021413061077700225410ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H #include #include #include #include "ptrholder.hpp" #include #include #include class btCollisionShape; class btCollisionObject; class btConvexShape; namespace Resource { class BulletShape; } namespace MWPhysics { class PhysicsTaskScheduler; class Actor final : public PtrHolder { public: Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk); ~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.load(std::memory_order_acquire); } 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 updateRotation(); /** * 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); osg::Vec3f getSimulationPosition() const; 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, bool ignoreCollisions); // apply position offset. Can't be called during simulation void applyOffsetChange(); osg::Vec3f getPosition() const; osg::Vec3f getPreviousPosition() const; /** * 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 mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire); } void setOnSlope(bool slope); bool getOnSlope() const { return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire); } btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } /// 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 skipCollisions(); void setVelocity(osg::Vec3f velocity); osg::Vec3f velocity(); 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; std::atomic mWalkingOnWater; bool mRotationallyInvariant; std::unique_ptr mShape; btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; osg::Vec3f mMeshTranslation; osg::Vec3f mHalfExtents; osg::Quat mRotation; osg::Vec3f mScale; osg::Vec3f mRenderingScale; osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; osg::Vec3f mVelocity; bool mWorldPositionChanged; bool mSkipCollisions; bool mSkipSimulation; mutable std::mutex mPositionMutex; unsigned int mStuckFrames; osg::Vec3f mLastStuckPosition; osg::Vec3f mForce; std::atomic mOnGround; std::atomic mOnSlope; std::atomic mInternalCollisionMode; bool mExternalCollisionMode; PhysicsTaskScheduler* mTaskScheduler; Actor(const Actor&); Actor& operator=(const Actor&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/actorconvexcallback.cpp000066400000000000000000000114331413061077700254340ustar00rootroot00000000000000#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); auto* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); if (projectileHolder->isValidTarget(target)) projectileHolder->hit(target, 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.47.0/apps/openmw/mwphysics/actorconvexcallback.hpp000066400000000000000000000013651413061077700254440ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp000066400000000000000000000024051413061077700275720ustar00rootroot00000000000000#include "closestnotmerayresultcallback.hpp" #include #include #include #include "../mwworld/class.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) { if (rayResult.m_collisionObject == mMe) return 1.f; if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) { auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) return 1.f; } } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } openmw-openmw-0.47.0/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp000066400000000000000000000014201413061077700275730ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/collisiontype.hpp000066400000000000000000000005241413061077700243250ustar00rootroot00000000000000#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 }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/constants.hpp000066400000000000000000000024531413061077700234470ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_CONSTANTS_H #define OPENMW_MWPHYSICS_CONSTANTS_H namespace MWPhysics { static constexpr float sStepSizeUp = 34.0f; 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; static constexpr float sMaxSlope = 49.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.1f; // 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.47.0/apps/openmw/mwphysics/contacttestresultcallback.cpp000066400000000000000000000021741413061077700266750ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/contacttestresultcallback.hpp000066400000000000000000000015441413061077700267020ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/contacttestwrapper.cpp000066400000000000000000000014431413061077700253600ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/contacttestwrapper.h000066400000000000000000000010671413061077700250270ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp000066400000000000000000000034661413061077700313170ustar00rootroot00000000000000#include "deepestnotmecontacttestresultcallback.hpp" #include #include #include "../mwworld/class.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 (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) { PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) 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.47.0/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp000066400000000000000000000022521413061077700313140ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/hasspherecollisioncallback.hpp000066400000000000000000000047071413061077700270120ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H #define OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H #include #include #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::max(aabbMin.x(), std::min(aabbMax.x(), position.x())), std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())), std::max(aabbMin.z(), std::min(aabbMax.z(), position.z())) ); return nearest.distance(position) < radius; } class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, const int mask, const int group) : mPosition(position), mRadius(radius), mCollisionObject(object), mCollisionFilterMask(mask), mCollisionFilterGroup(group) { } bool process(const btBroadphaseProxy* proxy) override { if (mResult) return false; const auto collisionObject = static_cast(proxy->m_clientObject); if (collisionObject == mCollisionObject) return true; if (needsCollision(*proxy)) mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); return !mResult; } bool getResult() const { return mResult; } private: btVector3 mPosition; btScalar mRadius; btCollisionObject* mCollisionObject; int mCollisionFilterMask; int mCollisionFilterGroup; 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.47.0/apps/openmw/mwphysics/heightfield.cpp000066400000000000000000000074051413061077700237040ustar00rootroot00000000000000#include "heightfield.hpp" #include "mtphysics.hpp" #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, float sqrtVerts) -> std::enable_if_t::value, std::vector> { return {}; } template auto makeHeights(const T* heights, float sqrtVerts) -> std::enable_if_t::value, std::vector> { return std::vector(heights, heights + static_cast(sqrtVerts * sqrtVerts)); } 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() {} HeightField::HeightField(const HeightField&, const osg::CopyOp&) {} HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) : mHoldObject(holdObject) #if BT_BULLET_VERSION < 310 , mHeights(makeHeights(heights, sqrtVerts)) #endif , mTaskScheduler(scheduler) { #if BT_BULLET_VERSION < 310 mShape = std::make_unique( sqrtVerts, sqrtVerts, getHeights(heights, mHeights), 1, minH, maxH, 2, PHY_FLOAT, false ); #else mShape = std::make_unique( sqrtVerts, sqrtVerts, heights, minH, maxH, 2, false); #endif mShape->setUseDiamondSubdivision(true); mShape->setLocalScaling(btVector3(triSize, triSize, 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 btTransform transform(btQuaternion::getIdentity(), btVector3((x+0.5f) * triSize * (sqrtVerts-1), (y+0.5f) * triSize * (sqrtVerts-1), (maxH+minH)*0.5f)); 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.47.0/apps/openmw/mwphysics/heightfield.hpp000066400000000000000000000024501413061077700237040ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H #define OPENMW_MWPHYSICS_HEIGHTFIELD_H #include #include #include #include #include class btCollisionObject; class btHeightfieldTerrainShape; namespace osg { class Object; } namespace MWPhysics { class PhysicsTaskScheduler; class HeightField : public osg::Object { public: HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); META_Object(MWPhysics, 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; HeightField(); HeightField(const HeightField&, const osg::CopyOp&); void operator=(const HeightField&); HeightField(const HeightField&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/movementsolver.cpp000066400000000000000000000616141413061077700245170ustar00rootroot00000000000000#include "movementsolver.hpp" #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 "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 = 0xff; 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, WorldFrameData& worldData) { auto* physicActor = actor.mActorRaw; const ESM::Position& refpos = actor.mRefpos; // Early-out for totally static creatures // (Not sure if gravity should still apply?) { const auto ptr = physicActor->getPtr(); if (!ptr.getClass().isMobile(ptr)) return; } // Reset per-frame data physicActor->setWalkingOnWater(false); // Anything to collide with? if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) { actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) ) * actor.mMovement * time; return; } const btCollisionObject *colobj = physicActor->getCollisionObject(); // 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() osg::Vec3f halfExtents = physicActor->getHalfExtents(); actor.mPosition.z() += halfExtents.z(); // vanilla-accurate static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); ActorTracer tracer; osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; if (actor.mPosition.z() < swimlevel || actor.mFlying) { velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; } else { velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope())) inertia = velocity; else if (!physicActor->getOnGround() || physicActor->getOnSlope()) velocity = velocity + inertia; } // Dead and paralyzed actors underwater will float to the surface, // if the CharacterController tells us to do so if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; if (actor.mWantJump) actor.mDidJump = true; // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm) { 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, colobj); 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; bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; 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; // If not able to fly, don't allow to swim up into the air if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) { 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(colobj, newPosition, nextpos, collisionWorld); // 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; } if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) seenGround = true; // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; if (hitHeight < 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) { // don't let pure water creatures move out of water after stepMove const auto ptr = physicActor->getPtr(); if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > 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; // 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 if(numTimesSlid > 0) { auto dotA = lastSlideNormal * planeNormal; auto dotB = lastSlideNormalFallback * planeNormal; 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 ^ planeNormal; // 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 + planeNormal; averageNormal.normalize(); tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; usedSeamLogic = true; } } } // otherwise just keep the normal vector rejection // if this isn't the first iteration, or if the first iteration is also the last iteration, // 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 && (iterations > 0 || remainingTime < 0.01f)) { tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } // Do not allow sliding up steep slopes if there is gravity. if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) newVelocity.z() = std::min(newVelocity.z(), velocity.z()); if (newVelocity * origVelocity <= 0.0f) break; numTimesSlid += 1; lastSlideNormalFallback = lastSlideNormal; lastSlideNormal = planeNormal; velocity = newVelocity; } } bool isOnGround = false; bool isOnSlope = false; if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(colobj, from, to, collisionWorld); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) { isOnGround = true; isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); const btCollisionObject* standingOn = tracer.mHitObject; PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); if (ptrHolder) actor.mStandingOn = ptrHolder->getPtr(); if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) physicActor->setWalkingOnWater(true); if (!actor.mFlying && !isOnSlope) { if (tracer.mFraction*dropDistance > sGroundOffset) newPosition.z() = tracer.mEndPos.z() + sGroundOffset; else { newPosition.z() = tracer.mEndPos.z(); tracer.doTrace(colobj, 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(physicActor->getStuckFrames() > 0) { isOnGround = true; isOnSlope = false; } } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); else { inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; if (inertia.z() < 0) inertia.z() *= actor.mSlowFall; if (actor.mSlowFall < 1.f) { inertia.x() *= actor.mSlowFall; inertia.y() *= actor.mSlowFall; } physicActor->setInertialForce(inertia); } physicActor->setOnGround(isOnGround); physicActor->setOnSlope(isOnSlope); actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate } btVector3 addMarginToDelta(btVector3 delta) { if(delta.length2() == 0.0) return delta; return delta + delta.normalized() * sCollisionMargin; } void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { const auto& ptr = actor.mActorRaw->getPtr(); if (!ptr.getClass().isMobile(ptr)) return; auto* physicActor = actor.mActorRaw; if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl return; auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; if(physicActor->getStuckFrames() >= 10) { if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) return; else { physicActor->setStuckFrames(0); physicActor->setLastStuckPosition({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, physicActor->getHalfExtents().z()); // use a 3d approximation of the movement vector to better judge player intent auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], 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 (!physicActor->getOnGround() || physicActor->getOnSlope()) velocity += physicActor->getInertialForce(); // 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 = collisionObject->getWorldTransform(); btTransform newTransform = oldTransform; auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback { goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); collisionObject->setWorldTransform(newTransform); ContactCollectionCallback callback{collisionObject, velocity}; ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, 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) { physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); physicActor->setLastStuckPosition(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 { physicActor->setStuckFrames(0); physicActor->setLastStuckPosition({0, 0, 0}); } collisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; } } openmw-openmw-0.47.0/apps/openmw/mwphysics/movementsolver.hpp000066400000000000000000000024441413061077700245200ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_MOVEMENTSOLVER_H #define OPENMW_MWPHYSICS_MOVEMENTSOLVER_H #include #include "constants.hpp" #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(sMaxSlope)); return (normal.z() > sMaxSlopeCos); } class Actor; struct ActorFrameData; 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, WorldFrameData& worldData); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/mtphysics.cpp000066400000000000000000000610361413061077700234530ustar00rootroot00000000000000#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 "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "actor.hpp" #include "contacttestwrapper.h" #include "movementsolver.hpp" #include "mtphysics.hpp" #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" namespace { /// @brief A scoped lock that is either shared or exclusive depending on configuration template class MaybeSharedLock { public: /// @param mutex a shared mutex /// @param canBeSharedLock decide wether the lock will be shared or exclusive MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock) { if (mCanBeSharedLock) mMutex.lock_shared(); else mMutex.lock(); } ~MaybeSharedLock() { if (mCanBeSharedLock) mMutex.unlock_shared(); else mMutex.unlock(); } private: Mutex& mMutex; bool mCanBeSharedLock; }; void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) { const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) actorData.mNeedLand = true; else if (heightDiff < 0) actorData.mFallHeight += heightDiff; } void handleJump(const MWWorld::Ptr &ptr) { 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; } void updateMechanics(MWPhysics::ActorFrameData& actorData) { auto ptr = actorData.mActorRaw->getPtr(); if (actorData.mDidJump) handleJump(ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); else if (actorData.mFallHeight < 0) stats.addToFallHeight(-actorData.mFallHeight); } osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading int computeNumThreads(bool& threadSafeBullet) { int wantedThread = Settings::Manager::getInt("async num threads", "Physics"); auto broad = std::make_unique(); auto maxSupportedThreads = broad->m_rayTestStacks.size(); threadSafeBullet = (maxSupportedThreads > 1); if (!threadSafeBullet && wantedThread > 1) { Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; return 1; } return std::max(0, wantedThread); } } } namespace MWPhysics { PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) , mDebugDrawer(debugDrawer) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) , mDeferAabbUpdate(Settings::Manager::getBool("defer aabb update", "Physics")) , mNewFrame(false) , mAdvanceSimulation(false) , mQuit(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) { mNumThreads = Config::computeNumThreads(mThreadSafeBullet); if (mNumThreads >= 1) { for (int i = 0; i < mNumThreads; ++i) mThreads.emplace_back([&] { worker(); } ); } else { mLOSCacheExpiry = -1; mDeferAabbUpdate = false; } mPreStepBarrier = std::make_unique(mNumThreads); mPostStepBarrier = std::make_unique(mNumThreads); mPostSimBarrier = std::make_unique(mNumThreads); } PhysicsTaskScheduler::~PhysicsTaskScheduler() { std::unique_lock lock(mSimulationMutex); mQuit = true; mNumJobs = 0; mRemainingSteps = 0; lock.unlock(); mHasJob.notify_all(); 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); } const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, 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. std::unique_lock lock(mSimulationMutex); double timeStart = mTimer->tick(); mMovedActors.clear(); // start by finishing previous background computation if (mNumThreads != 0) { for (auto& data : mActorsFrameData) { const auto actorActive = [&data](const auto& newFrameData) -> bool { const auto actor = data.mActor.lock(); return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr(); }; // Only return actors that are still part of the scene if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) { updateMechanics(data); // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values if (mAdvanceSimulation) data.mActorRaw->setStandingOnPtr(data.mStandingOn); data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); mMovedActors.emplace_back(data.mActorRaw->getPtr()); } } if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); } auto [numSteps, newDelta] = calculateStepConfig(timeAccum); timeAccum -= numSteps*newDelta; // init for (auto& data : actorsData) data.updatePosition(mCollisionWorld); mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; mNumJobs = mActorsFrameData.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) { syncComputation(); if(mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return mMovedActors; } mAsyncStartTime = mTimer->tick(); lock.unlock(); mHasJob.notify_all(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); return mMovedActors; } const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); mMovedActors.clear(); mActorsFrameData.clear(); for (const auto& [_, actor] : actors) { actor->updatePosition(); actor->updateCollisionObjectPosition(); mMovedActors.emplace_back(actor->getPtr()); } return mMovedActors; } void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::shared_lock lock(mCollisionWorldMutex); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); // 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) { std::shared_lock lock(mCollisionWorldMutex); mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); } void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) { std::shared_lock lock(mCollisionWorldMutex); obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); } void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) { std::unique_lock lock(mCollisionWorldMutex); collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; } void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->removeCollisionObject(collisionObject); } void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate) { if (!mDeferAabbUpdate || immediate) { updatePtrAabb(ptr); } else { std::unique_lock lock(mUpdateAabbMutex); mUpdateAabb.insert(std::move(ptr)); } } bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2) { std::unique_lock lock(mLOSCacheMutex); auto actorPtr1 = actor1.lock(); auto actorPtr2 = actor2.lock(); if (!actorPtr1 || !actorPtr2) return false; auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); if (result == mLOSCache.end()) { req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get()); if (mLOSCacheExpiry >= 0) mLOSCache.push_back(req); return req.mResult; } result->mAge = 0; return result->mResult; } void PhysicsTaskScheduler::refreshLOSCache() { std::shared_lock lock(mLOSCacheMutex); 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() { std::scoped_lock lock(mUpdateAabbMutex); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); } void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr& ptr) { if (const auto p = ptr.lock()) { std::scoped_lock lock(mCollisionWorldMutex); if (const auto actor = std::dynamic_pointer_cast(p)) { actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } else if (const auto object = std::dynamic_pointer_cast(p)) { object->commitPositionChange(); mCollisionWorld->updateSingleAabb(object->getCollisionObject()); } else if (const auto projectile = std::dynamic_pointer_cast(p)) { projectile->commitPositionChange(); mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); } }; } void PhysicsTaskScheduler::worker() { std::shared_lock lock(mSimulationMutex); while (!mQuit) { if (!mNewFrame) mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { if(const auto actor = mActorsFrameData[job].mActor.lock()) { MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } } mPostStepBarrier->wait([this] { afterPostStep(); }); if (!mRemainingSteps) { while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { if(const auto actor = mActorsFrameData[job].mActor.lock()) { auto& actorData = mActorsFrameData[job]; handleFall(actorData, mAdvanceSimulation); } } if (mLOSCacheExpiry >= 0) refreshLOSCache(); mPostSimBarrier->wait([this] { afterPostSim(); }); } } } void PhysicsTaskScheduler::updateActorsPositions() { for (auto& actorData : mActorsFrameData) { if(const auto actor = actorData.mActor.lock()) { if (actor->setPosition(actorData.mPosition)) { std::scoped_lock lock(mCollisionWorldMutex); actorData.mPosition = actor->getPosition(); // account for potential position change made by script actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } } } 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 = 0xFF; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); } void PhysicsTaskScheduler::syncComputation() { while (mRemainingSteps--) { for (auto& actorData : mActorsFrameData) { MovementSolver::unstuck(actorData, mCollisionWorld); MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); } updateActorsPositions(); } for (auto& actorData : mActorsFrameData) { handleFall(actorData, mAdvanceSimulation); actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); mMovedActors.emplace_back(actorData.mActorRaw->getPtr()); if (mAdvanceSimulation) actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } } 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() { std::shared_lock lock(mCollisionWorldMutex); mDebugDrawer->step(); } void PhysicsTaskScheduler::afterPreStep() { if (mDeferAabbUpdate) updateAabbs(); if (!mRemainingSteps) return; for (auto& data : mActorsFrameData) if (const auto actor = data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); MovementSolver::unstuck(data, mCollisionWorld); } } void PhysicsTaskScheduler::afterPostStep() { if (mRemainingSteps) { --mRemainingSteps; updateActorsPositions(); } mNextJob.store(0, std::memory_order_release); } void PhysicsTaskScheduler::afterPostSim() { mNewFrame = false; if (mLOSCacheExpiry >= 0) { std::unique_lock lock(mLOSCacheMutex); mLOSCache.erase( std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } mTimeEnd = mTimer->tick(); } } openmw-openmw-0.47.0/apps/openmw/mwphysics/mtphysics.hpp000066400000000000000000000120351413061077700234530ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_MTPHYSICS_H #define OPENMW_MWPHYSICS_MTPHYSICS_H #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 { 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 const std::vector& moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector& 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(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); void debugDraw(); private: void syncComputation(); void worker(); void updateActorsPositions(); bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); void updatePtrAabb(const std::weak_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(); std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; std::vector mMovedActors; 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; int mNumThreads; int mNumJobs; int mRemainingSteps; int mLOSCacheExpiry; bool mDeferAabbUpdate; bool mNewFrame; bool mAdvanceSimulation; bool mThreadSafeBullet; bool mQuit; 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; std::condition_variable_any mHasJob; 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/object.cpp000066400000000000000000000127021413061077700226720ustar00rootroot00000000000000#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, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) { mPtr = ptr; mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(ptr.getRefData().getBaseNode()->getAttitude()); updatePosition(); commitPositionChange(); 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(const 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; } } btCollisionObject* Object::getCollisionObject() { return mCollisionObject.get(); } const btCollisionObject* Object::getCollisionObject() const { return mCollisionObject.get(); } 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->mAnimatedShapes.empty(); } bool Object::animateCollisionShapes() { if (mShapeInstance->mAnimatedShapes.empty()) return false; assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); 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); } return true; } } openmw-openmw-0.47.0/apps/openmw/mwphysics/object.hpp000066400000000000000000000033331413061077700226770ustar00rootroot00000000000000#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, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); void setRotation(const osg::Quat& quat); void updatePosition(); void commitPositionChange(); btCollisionObject* getCollisionObject(); const btCollisionObject* getCollisionObject() const; 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: std::unique_ptr mCollisionObject; osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; btVector3 mScale; osg::Vec3f mPosition; osg::Quat mRotation; bool mScaleUpdatePending; bool mTransformUpdatePending; mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/physicssystem.cpp000066400000000000000000001114671413061077700243630ustar00rootroot00000000000000#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 "../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 { bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) { if (!physicActor) return false; const float halfZ = physicActor->getHalfExtents().z(); const osg::Vec3f actorPosition = physicActor->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(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); return (tracer.mFraction >= 1.0f); } } namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) : mShapeManager(new Resource::BulletShapeManager(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) { 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()); mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); } void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) { mUnrefQueue = unrefQueue; } 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); 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()) 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); 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, 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 (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) const { btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); callback.m_collisionFilterGroup = 0xff; callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; 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); } return result; } bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr { const auto found = mActors.find(ptr); if (found != mActors.end()) return { found->second }; return {}; }; return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2)); } bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) { Actor* physactor = getActor(actor); return physactor && physactor->getOnGround(); } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { return ::canMoveToWaterSurface(getActor(actor), 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); 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); 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, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { mHeightFields[std::make_pair(x,y)] = osg::ref_ptr(new HeightField(heights, x, y, triSize, sqrtVerts, 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, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { if (mUnrefQueue.get()) mUnrefQueue->push(found->second->getShapeInstance()); mAnimatedObjects.erase(found->second.get()); mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); if (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) { ObjectMap::iterator found = mObjects.find(old); if (found != mObjects.end()) { auto obj = found->second; obj->updatePtr(updated); mObjects.erase(found); mObjects.emplace(updated, std::move(obj)); } ActorMap::iterator foundActor = mActors.find(old); if (foundActor != mActors.end()) { auto actor = foundActor->second; actor->updatePtr(updated); mActors.erase(foundActor); mActors.emplace(updated, std::move(actor)); } 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); 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); 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); 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) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { float scale = ptr.getCellRef().getScale(); found->second->setScale(scale); mTaskScheduler->updateSingleAabb(found->second); return; } ActorMap::iterator foundActor = mActors.find(ptr); if (foundActor != mActors.end()) { foundActor->second->updateScale(); mTaskScheduler->updateSingleAabb(foundActor->second); return; } } void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const { const auto foundProjectile = mProjectiles.find(projectileId); assert(foundProjectile != mProjectiles.end()); auto* projectile = foundProjectile->second.get(); btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); btVector3 btTo = Misc::Convert::toBullet(position); if (btFrom == btTo) return; const auto casterPtr = projectile->getCaster(); const auto* caster = [this,&casterPtr]() -> const btCollisionObject* { const Actor* actor = getActor(casterPtr); if (actor) return actor->getCollisionObject(); const Object* object = getObject(casterPtr); if (object) return object->getCollisionObject(); return nullptr; }(); ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; const btQuaternion btrot = btQuaternion::getIdentity(); btTransform from_ (btrot, btFrom); btTransform to_ (btrot, btTo); mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); projectile->setPosition(newpos); mTaskScheduler->updateSingleAabb(foundProjectile->second); } void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { found->second->setRotation(ptr.getRefData().getBaseNode()->getAttitude()); mTaskScheduler->updateSingleAabb(found->second); return; } ActorMap::iterator foundActor = mActors.find(ptr); if (foundActor != mActors.end()) { if (!foundActor->second->isRotationallyInvariant()) { foundActor->second->updateRotation(); mTaskScheduler->updateSingleAabb(foundActor->second); } return; } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); return; } ActorMap::iterator foundActor = mActors.find(ptr); if (foundActor != mActors.end()) { foundActor->second->updatePosition(); mTaskScheduler->updateSingleAabb(foundActor->second, true); return; } } 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.extents.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); // check if Actor is on the ground or in the air traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); mActors.emplace(ptr, 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.extents.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()); 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); if (found != mActors.end()) found->second->setVelocity(velocity); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) actor->setVelocity(osg::Vec3f()); } const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { mTimeAccum += dt; if (skipSimulation) return mTaskScheduler->resetSimulation(mActors); // modifies mTimeAccum return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); } std::vector PhysicsSystem::prepareFrameData(bool willSimulate) { std::vector actorsFrameData; actorsFrameData.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ptr, physicActor] : mActors) { float waterlevel = -std::numeric_limits::max(); const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(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::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. MWWorld::Ptr standingOn; if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); } return actorsFrameData; } void PhysicsSystem::stepSimulation() { for (Object* animatedObject : mAnimatedObjects) if (animatedObject->animateCollisionShapes()) { auto obj = mObjects.find(animatedObject->getPtr()); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); } #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); #endif } void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) { ObjectMap::iterator found = mObjects.find(object); 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); 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.reset(new btCollisionObject()); mWaterCollisionShape.reset(new btStaticPlaneShape(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 MWWorld::ConstPtr& ignore) const { btCollisionObject* object = nullptr; const auto it = mActors.find(ignore); if (it != mActors.end()) object = it->second->getCollisionObject(); 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 = 0xff; HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); 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 HeightFields", mHeightFields.size()); } void PhysicsSystem::reportCollision(const btVector3& position, const btVector3& normal) { if (mDebugDrawEnabled) mDebugDrawer->addCollision(position, normal); } ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); mSwimming = world->isSwimming(ptr); mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); mWasOnGround = actor->getOnGround(); } void ActorFrameData::updatePosition(btCollisionWorld* world) { mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) { mPosition.z() = mWaterlevel; MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); } 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.47.0/apps/openmw/mwphysics/physicssystem.hpp000066400000000000000000000274651413061077700243740ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #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; } namespace SceneUtil { class UnrefQueue; } 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::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(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(btCollisionWorld* world); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mStandingOn; bool mFlying; bool mSwimming; bool mWasOnGround; bool mWantJump; bool mDidJump; bool mFloatToSurface; bool mNeedLand; bool mWaterCollision; bool mSkipCollisionDetection; float mWaterlevel; float mSlowFall; float mOldHeight; float mFallHeight; osg::Vec3f mMovement; osg::Vec3f mPosition; ESM::Position mRefpos; }; struct WorldFrameData { WorldFrameData(); bool mIsInStorm; osg::Vec3f mStormDirection; }; class PhysicsSystem : public RayCastingInterface { public: PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); virtual ~PhysicsSystem (); void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); Resource::BulletShapeManager* getShapeManager(); void enableWater(float height); void setWaterHeight(float height); void disableWater(); void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, 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 updateProjectile(const int projectileId, const osg::Vec3f &position) const; 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); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); void removeHeightField (int x, int y); const HeightField* getHeightField(int x, int y) const; bool toggleCollisionMode(); void stepSimulation(); 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(), std::vector targets = std::vector(), int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) 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 applyQueuedMovement. void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Apply all queued movements, then clear the list. const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); /// 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 MWWorld::ConstPtr& ignore) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); private: void updateWater(); std::vector prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; 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::map>; ObjectMap mObjects; std::set mAnimatedObjects; // stores pointers to elements in mObjects ActorMap mActors; using ProjectileMap = std::map>; ProjectileMap mProjectiles; using HeightFieldMap = std::map, osg::ref_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; PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/projectile.cpp000066400000000000000000000063041413061077700235650ustar00rootroot00000000000000#include #include #include #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "mtphysics.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) , mCaster(caster) , 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); setPosition(position); const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); commitPositionChange(); } Projectile::~Projectile() { if (!mActive) mPhysics->reportCollision(mHitPosition, mHitNormal); mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Projectile::commitPositionChange() { std::scoped_lock lock(mMutex); if (mTransformUpdatePending) { auto& trans = mCollisionObject->getWorldTransform(); trans.setOrigin(Misc::Convert::toBullet(mPosition)); mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } void Projectile::setPosition(const osg::Vec3f &position) { std::scoped_lock lock(mMutex); mPosition = position; mTransformUpdatePending = true; } osg::Vec3f Projectile::getPosition() const { std::scoped_lock lock(mMutex); return mPosition; } void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) return; std::scoped_lock lock(mMutex); mHitTarget = target; mHitPosition = pos; mHitNormal = normal; mActive.store(false, std::memory_order_release); } MWWorld::Ptr Projectile::getCaster() const { std::scoped_lock lock(mMutex); return mCaster; } void Projectile::setCaster(MWWorld::Ptr caster) { std::scoped_lock lock(mMutex); mCaster = caster; } void Projectile::setValidTargets(const std::vector& targets) { std::scoped_lock lock(mMutex); mValidTargets = targets; } bool Projectile::isValidTarget(const MWWorld::Ptr& target) const { std::scoped_lock lock(mMutex); if (mCaster == target) return false; if (target.isEmpty() || mValidTargets.empty()) return true; bool validTarget = false; for (const auto& targetActor : mValidTargets) { if (targetActor == target) { validTarget = true; break; } } return validTarget; } } openmw-openmw-0.47.0/apps/openmw/mwphysics/projectile.hpp000066400000000000000000000046501413061077700235740ustar00rootroot00000000000000#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 { class 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 commitPositionChange(); void setPosition(const osg::Vec3f& position); osg::Vec3f getPosition() const; btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } bool isActive() const { return mActive.load(std::memory_order_acquire); } MWWorld::Ptr getTarget() const { assert(!mActive); return mHitTarget; } MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); void setHitWater() { mHitWater = true; } bool getHitWater() const { return mHitWater; } void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; btVector3 getHitPosition() const { return mHitPosition; } private: std::unique_ptr mShape; btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; bool mTransformUpdatePending; bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; osg::Vec3f mPosition; 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.47.0/apps/openmw/mwphysics/projectileconvexcallback.cpp000066400000000000000000000051031413061077700264610ustar00rootroot00000000000000#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* me, const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestConvexResultCallback(from, to) , mMe(me), mProjectile(proj) { assert(mProjectile); } btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { // don't hit the caster if (result.m_hitCollisionObject == mMe) return 1.f; // don't hit the projectile if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) return 1.f; btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) { case CollisionType_Actor: { auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); if (!mProjectile->isValidTarget(target->getPtr())) return 1.f; mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); break; } case CollisionType_Projectile: { auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); if (!mProjectile->isValidTarget(target->getCaster())) return 1.f; target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: { mProjectile->setHitWater(); mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); break; } default: { auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); auto ptr = target ? target->getPtr() : MWWorld::Ptr(); mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } return result.m_hitFraction; } } openmw-openmw-0.47.0/apps/openmw/mwphysics/projectileconvexcallback.hpp000066400000000000000000000012621413061077700264700ustar00rootroot00000000000000#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* me, const btVector3& from, const btVector3& to, Projectile* proj); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; Projectile* mProjectile; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/ptrholder.hpp000066400000000000000000000013161413061077700234330ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H #include #include "../mwworld/ptr.hpp" namespace MWPhysics { class PtrHolder { public: virtual ~PtrHolder() {} void updatePtr(const MWWorld::Ptr& updated) { std::scoped_lock lock(mMutex); mPtr = updated; } MWWorld::Ptr getPtr() { std::scoped_lock lock(mMutex); return mPtr; } MWWorld::ConstPtr getPtr() const { std::scoped_lock lock(mMutex); return mPtr; } protected: MWWorld::Ptr mPtr; private: mutable std::mutex mMutex; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwphysics/raycasting.hpp000066400000000000000000000033201413061077700235710ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_RAYCASTING_H #define OPENMW_MWPHYSICS_RAYCASTING_H #include #include "../mwworld/ptr.hpp" #include "collisiontype.hpp" namespace MWPhysics { struct RayCastingResult { 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(), std::vector targets = std::vector(), int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) 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.47.0/apps/openmw/mwphysics/stepper.cpp000066400000000000000000000161361413061077700231130ustar00rootroot00000000000000#include "stepper.hpp" #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(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,sStepSizeUp), mColWorld); float upDistance = 0; if(!mUpStepper.mHitObject) upDistance = sStepSizeUp; else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin) upDistance = mUpStepper.mFraction*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 > sStepSizeUp) { upDistance = 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); // 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.47.0/apps/openmw/mwphysics/stepper.hpp000066400000000000000000000011551413061077700231130ustar00rootroot00000000000000#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.47.0/apps/openmw/mwphysics/trace.cpp000066400000000000000000000063171413061077700225270ustar00rootroot00000000000000#include "trace.h" #include #include #include #include "collisiontype.hpp" #include "actor.hpp" #include "actorconvexcallback.hpp" namespace MWPhysics { void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { const btVector3 btstart = Misc::Convert::toBullet(start); const btVector3 btend = Misc::Convert::toBullet(end); const btTransform &trans = actor->getWorldTransform(); btTransform from(trans); btTransform to(trans); from.setOrigin(btstart); to.setOrigin(btend); const btVector3 motion = btstart-btend; ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; const btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); // Copy the hit data over to our trace results struct: 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; } else { 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 btVector3 btstart = Misc::Convert::toBullet(start); const btVector3 btend = Misc::Convert::toBullet(end); const btTransform &trans = actor->getCollisionObject()->getWorldTransform(); btTransform from(trans.getBasis(), btstart); btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback); if(newTraceCallback.hasHit()) { mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.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.47.0/apps/openmw/mwphysics/trace.h000066400000000000000000000012021413061077700221600ustar00rootroot00000000000000#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); void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/000077500000000000000000000000001413061077700205135ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwrender/.gitignore000066400000000000000000000000041413061077700224750ustar00rootroot00000000000000old openmw-openmw-0.47.0/apps/openmw/mwrender/actoranimation.cpp000066400000000000000000000517121413061077700242350ustar00rootroot00000000000000#include "actoranimation.hpp" #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 "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() { for (ItemLightMap::iterator iter = mItemLights.begin(); iter != mItemLights.end(); ++iter) { mInsert->removeChild(iter->second); } mScabbard.reset(); mHolsteredShield.reset(); } 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(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) return PartHolderPtr(); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(instance)); } std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const { std::string mesh = shield.getClass().getModel(shield); const ESM::Armor *armor = shield.get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; if (!bodyparts.empty()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); // Try to get shield model from bodyparts first, with ground model as fallback for (const auto& part : bodyparts) { // Assume all creatures use the male mesh. if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) continue; const ESM::BodyPart *bodypart = partStore.search(part.mMale); if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) { mesh = "meshes\\" + bodypart->mModel; break; } } } 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) && weaptype != ESM::Weapon::Spell) { SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); mObjectRoot->accept(findVisitor); if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) { 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 (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) { if(stats.getDrawState() != MWMechanics::DrawState_Weapon) return false; if (weapon != inv.end()) { const std::string &type = weapon->getTypeName(); if(type == typeid(ESM::Weapon).name()) { 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 == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) return true; } } } } } 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->getTypeName() != typeid(ESM::Armor).name()) 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; const std::string &type = weapon->getTypeName(); if(type == typeid(ESM::Weapon).name()) { 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 = getShieldMesh(*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); } if (mAlpha != 1.f) mResourceSystem->getSceneManager()->recreateShaders(mHolsteredShield->getNode()); } 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->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) { const std::string &type = weapon->getTypeName(); if(type == typeid(ESM::Weapon).name()) { 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 == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) return true; } return false; } osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) { 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; const std::string &type = weapon.getClass().getTypeName(); if(type == typeid(ESM::Weapon).name()) { 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; std::shared_ptr src; src.reset(new NullAnimationTime); SceneUtil::AssignControllerSourcesVisitor removeVisitor(src); 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->getTypeName() != typeid(ESM::Weapon).name()) 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->getTypeName() != typeid(ESM::Weapon).name()) 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.getTypeName() == typeid(ESM::Light).name()) { 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->getTypeName() != typeid(ESM::Weapon).name()) 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.getTypeName() == typeid(ESM::Light).name()) { 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->getTypeName() != typeid(ESM::Weapon).name()) 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); } } openmw-openmw-0.47.0/apps/openmw/mwrender/actoranimation.hpp000066400000000000000000000042251413061077700242370ustar00rootroot00000000000000#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; protected: osg::Group* getBoneByName(const std::string& boneName); virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); virtual std::string getShieldMesh(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); }; 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); typedef std::map > ItemLightMap; ItemLightMap mItemLights; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/actorspaths.cpp000066400000000000000000000051411413061077700235530ustar00rootroot00000000000000#include "actorspaths.hpp" #include "vismask.hpp" #include #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 osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::Settings& settings) { if (!mEnabled) return; const auto group = mGroups.find(actor); if (group != mGroups.end()) mRootNode->removeChild(group->second); const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); if (newGroup) { newGroup->setNodeMask(Mask_Debug); mRootNode->addChild(newGroup); mGroups[actor] = newGroup; } } void ActorsPaths::remove(const MWWorld::ConstPtr& actor) { const auto group = mGroups.find(actor); if (group != mGroups.end()) { mRootNode->removeChild(group->second); mGroups.erase(group); } } void ActorsPaths::removeCell(const MWWorld::CellStore* const store) { for (auto it = mGroups.begin(); it != mGroups.end(); ) { if (it->first.getCell() == store) { mRootNode->removeChild(it->second); it = mGroups.erase(it); } else ++it; } } void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { const auto it = mGroups.find(old); if (it == mGroups.end()) return; auto group = std::move(it->second); mGroups.erase(it); mGroups.insert(std::make_pair(updated, std::move(group))); } void ActorsPaths::enable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const Groups::value_type& v) { mRootNode->addChild(v.second); }); mEnabled = true; } void ActorsPaths::disable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); }); mEnabled = false; } } openmw-openmw-0.47.0/apps/openmw/mwrender/actorspaths.hpp000066400000000000000000000022051413061077700235560ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_AGENTSPATHS_H #define OPENMW_MWRENDER_AGENTSPATHS_H #include #include #include #include #include namespace osg { class Group; } 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 osg::Vec3f& halfExtents, 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: using Groups = std::map>; osg::ref_ptr mRootNode; Groups mGroups; bool mEnabled; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/animation.cpp000066400000000000000000002045531413061077700232070ustar00rootroot00000000000000#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 "../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 { /// 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 osg::NodeCallback { public: DayNightCallback() : mCurrentState(0) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0; if (newState != mCurrentState) { mCurrentState = newState; node->asSwitch()->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; } /// @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() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) { if (!it->second->removeChild(it->first)) Log(Debug::Error) << "Error removing " << it->first->getName(); } } protected: // typedef std::vector > RemoveVec; std::vector > mToRemove; }; 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 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 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; }; // Removes all drawables from a graph. class CleanObjectRootVisitor : public RemoveVisitor { public: void apply(osg::Drawable& drw) override { applyDrawable(drw); } void apply(osg::Group& node) override { applyNode(node); } void apply(osg::MatrixTransform& node) override { applyNode(node); } void apply(osg::Node& node) override { applyNode(node); } void 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 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); } }; class RemoveTriBipVisitor : public RemoveVisitor { public: void apply(osg::Drawable& drw) override { applyImpl(drw); } void apply(osg::Group& node) override { traverse(node); } void apply(osg::MatrixTransform& node) override { traverse(node); } void 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); } } }; } namespace MWRender { class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: TransparencyUpdater(const float alpha) : mAlpha(alpha) { } void setAlpha(const float alpha) { mAlpha = alpha; } void setLightSource(const osg::ref_ptr& lightSource) { mLightSource = lightSource; } 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); if (mLightSource) mLightSource->setActorFade(mAlpha); } private: float mAlpha; osg::ref_ptr mLightSource; }; 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 osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osg::MatrixTransform* transform = static_cast(node); osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); mat.setTrans(position); transform->setMatrix(mat); traverse(node, 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].reset(new AnimationTime); mLightListCallback = new SceneUtil::LightListCallback; } Animation::~Animation() { Animation::setLightEffect(0.f); if (mObjectRoot) mInsert->removeChild(mObjectRoot); } MWWorld::ConstPtr Animation::getPtr() const { return mPtr; } MWWorld::Ptr Animation::getPtr() { return mPtr; } 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) { const std::map& index = mResourceSystem->getVFS()->getIndex(); std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } animationPath.replace(animationPath.size()-3, 3, "/"); mResourceSystem->getVFS()->normalizeFilename(animationPath); std::map::const_iterator found = index.lower_bound(animationPath); while (found != index.end()) { const std::string& name = found->first; if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0) addSingleAnimSource(name, baseModel); } else break; ++found; } } 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; std::shared_ptr animsrc; animsrc.reset(new AnimSource); animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) return; const NodeMap& nodeMap = getNodeMap(); for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); it != animsrc->mKeyframes->mKeyframeControllers.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(animsrc); SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); mObjectRoot->accept(assignVisitor); if (!mAccumRoot) { NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) found = nodeMap.find("root bone"); if (found != nodeMap.end()) mAccumRoot = found->second; } } 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(const std::string &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 node->addUpdateCallback(it->second); mActiveControllers.emplace_back(node, it->second); 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; const std::map& index = resourceSystem->getVFS()->getIndex(); std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } animationPath.replace(animationPath.size()-4, 4, "/"); resourceSystem->getVFS()->normalizeFilename(animationPath); std::map::const_iterator found = index.lower_bound(animationPath); while (found != index.end()) { const std::string& name = found->first; if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) loadBonesFromFile(node, name, resourceSystem); } else break; ++found; } } 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->createInstance(created); } else return sceneMgr->createInstance(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_ParticleSystem, Mask_Lighting, exterior); } 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(Misc::StringUtils::lowerCase(bonename)); if (found == getNodeMap().end()) throw std::runtime_error("Can't find bone " + bonename); parentNode = found->second; } osg::ref_ptr trans = new osg::PositionAttitudeTransform; if (!mPtr.getClass().isNpc()) { osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mPtr) * 2.f / Constants::UnitsPerFoot); float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f }); trans->setScale(osg::Vec3f(scale, scale, scale)); } parentNode->addChild(trans); osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; node->accept(disableFreezeOnCullVisitor); node->setNodeMask(Mask_Effect); params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; params.mAnimTime = std::shared_ptr(new EffectAnimationTime); 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 { std::string lowerName = Misc::StringUtils::lowerCase(name); NodeMap::const_iterator found = getNodeMap().find(lowerName); 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); mTransparencyUpdater->setLightSource(mExtraLightSource); mObjectRoot->addCullCallback(mTransparencyUpdater); } else mTransparencyUpdater->setAlpha(alpha); } else { mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; } } 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(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; } // ------------------------------------------------------ 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.getTypeName() == typeid(ESM::Light).name() && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); if (!allowLight && mObjectRoot) { RemoveParticlesVisitor visitor; mObjectRoot->accept(visitor); visitor.remove(); } if (SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) { AddSwitchCallbacksVisitor visitor; mObjectRoot->accept(visitor); } if (ptr.getRefData().getCustomData() != nullptr && canBeHarvested()) { const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); if (!store.hasVisibleItems()) { HarvestVisitor visitor; mObjectRoot->accept(visitor); } } } bool ObjectAnimation::canBeHarvested() const { if (mPtr.getTypeName() != typeid(ESM::Container).name()) return false; const MWWorld::LiveCellRef* ref = mPtr.get(); if (!(ref->mBase->mFlags & ESM::Container::Organic)) return false; return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel); } Animation::AnimState::~AnimState() { } // ------------------------------ 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.47.0/apps/openmw/mwrender/animation.hpp000066400000000000000000000442321413061077700232100ustar00rootroot00000000000000#ifndef GAME_RENDER_ANIMATION_H #define GAME_RENDER_ANIMATION_H #include "../mwworld/ptr.hpp" #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(); osg::ref_ptr getNode() { return mNode; } private: osg::ref_ptr mNode; void operator= (const PartHolder&); PartHolder(const PartHolder&); }; typedef std::shared_ptr PartHolderPtr; 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 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(); 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]; // Stored in all lowercase for a case-insensitive lookup typedef std::map > NodeMap; 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(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(); public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); /// Must be thread safe virtual ~Animation(); MWWorld::ConstPtr getPtr() const; MWWorld::Ptr getPtr(); /// 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(const std::string &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; } 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 osg::NodeCallback { public: UpdateVfxCallback(EffectParams& params) : mFinished(false) , mParams(params) , mStartingTime(0) { } bool mFinished; EffectParams mParams; void operator()(osg::Node* node, osg::NodeVisitor* nv) override; private: double mStartingTime; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/bulletdebugdraw.cpp000066400000000000000000000077101413061077700244000ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "bulletdebugdraw.hpp" #include "vismask.hpp" namespace MWRender { DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode) : mParentNode(parentNode), mWorld(world) { setDebugMode(debugMode); } void DebugDrawer::createGeometry() { if (!mGeometry) { mGeometry = new osg::Geometry; mGeometry->setNodeMask(Mask_Debug); mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; mDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); mGeometry->setUseDisplayList(false); mGeometry->setVertexArray(mVertices); mGeometry->setColorArray(mColors); mGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); mGeometry->setDataVariance(osg::Object::DYNAMIC); mGeometry->addPrimitiveSet(mDrawArrays); mParentNode->addChild(mGeometry); auto* stateSet = new osg::StateSet; stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); mShapesRoot = new osg::Group; mShapesRoot->setStateSet(stateSet); mShapesRoot->setDataVariance(osg::Object::DYNAMIC); mShapesRoot->setNodeMask(Mask_Debug); mParentNode->addChild(mShapesRoot); } } void DebugDrawer::destroyGeometry() { if (mGeometry) { mParentNode->removeChild(mGeometry); mParentNode->removeChild(mShapesRoot); mGeometry = nullptr; mVertices = nullptr; mDrawArrays = nullptr; } } DebugDrawer::~DebugDrawer() { destroyGeometry(); } void DebugDrawer::step() { if (mDebugOn) { mVertices->clear(); mColors->clear(); mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); mWorld->debugDrawWorld(); showCollisions(); mDrawArrays->setCount(mVertices->size()); mVertices->dirty(); mColors->dirty(); mGeometry->dirtyBound(); } } void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btVector3 &color) { mVertices->push_back(Misc::Convert::toOsg(from)); mVertices->push_back(Misc::Convert::toOsg(to)); mColors->push_back({1,1,1,1}); mColors->push_back({1,1,1,1}); } 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)) { mVertices->push_back(Misc::Convert::toOsg(from)); mVertices->push_back(Misc::Convert::toOsg(to)); mColors->push_back({1,0,0,1}); mColors->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.47.0/apps/openmw/mwrender/bulletdebugdraw.hpp000066400000000000000000000037551413061077700244120ustar00rootroot00000000000000#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 mGeometry; osg::ref_ptr mVertices; osg::ref_ptr mColors; osg::ref_ptr mDrawArrays; 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 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.47.0/apps/openmw/mwrender/camera.cpp000066400000000000000000000455101413061077700224540ustar00rootroot00000000000000#include "camera.hpp" #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 osg::NodeCallback { public: UpdateRenderCameraCallback(MWRender::Camera* cam) : mCamera(cam) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osg::Camera* cam = static_cast(node); // traverse first to update animations, in case the camera is attached to an animated node traverse(node, nv); mCamera->updateCamera(cam); } private: MWRender::Camera* mCamera; }; } namespace MWRender { Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), mMode(Mode::Normal), mVanityAllowed(true), mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")), mNearest(30.f), mFurthest(800.f), mIsNearest(false), mHeight(124.f), mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")), mPitch(0.f), mYaw(0.f), mRoll(0.f), mVanityToggleQueued(false), mVanityToggleQueuedValue(false), mViewModeToggleQueued(false), mCameraDistance(0.f), mMaxNextCameraDistance(800.f), mFocalPointCurrentOffset(osg::Vec2d()), mFocalPointTargetOffset(osg::Vec2d()), mFocalPointTransitionSpeedCoef(1.f), mSkipFocalPointTransition(true), mPreviousTransitionInfluence(0.f), mSmoothedSpeed(0.f), mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), mDynamicCameraDistanceEnabled(false), mShowCrosshairInThirdPersonMode(false), mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")), mHeadBobbingOffset(0.f), mHeadBobbingWeight(0.f), mTotalMovement(0.f), mDeferredRotation(osg::Vec3f()), mDeferredRotationDisabled(false) { mCameraDistance = mBaseCameraDistance; mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } Camera::~Camera() { mCamera->removeUpdateCallback(mUpdateCallback); } osg::Vec3d Camera::getFocalPoint() 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 position = worldMat.getTrans(); if (isFirstPerson()) position.z() += mHeadBobbingOffset; else { position.z() += mHeight * mHeightScale; // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. // Needed because character's head can be a bit higher than collision area. position.z() -= 10.f; position += getFocalPointOffset() + mFocalPointAdjustment; } return position; } osg::Vec3d Camera::getFocalPointOffset() const { osg::Vec3d offset(0, 0, 10.f); offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); offset.z() += mFocalPointCurrentOffset.y(); return offset; } void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const { focal = getFocalPoint(); osg::Vec3d offset(0,0,0); if (!isFirstPerson()) { osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); } camera = focal + offset; } void Camera::updateCamera(osg::Camera *cam) { osg::Vec3d focal, position; getPosition(focal, position); osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1)); osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); cam->setViewMatrixAsLookAt(position, position + forward, up); } void Camera::updateHeadBobbing(float duration) { static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2; static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera"); static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera")); if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr)) mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f); else mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f); float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1 float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight; mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2 mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll } void Camera::reset() { togglePreviewMode(false); toggleVanityMode(false); if (!mFirstPersonView) toggleViewMode(); } void Camera::rotateCamera(float pitch, float yaw, bool adjust) { if (adjust) { pitch += getPitch(); yaw += getYaw(); } setYaw(yaw); setPitch(pitch); } void Camera::update(float duration, bool paused) { if (mAnimation->upperBodyReady()) { // Now process the view changes we queued earlier if (mVanityToggleQueued) { toggleVanityMode(mVanityToggleQueuedValue); mVanityToggleQueued = false; } if (mViewModeToggleQueued) { togglePreviewMode(false); toggleViewMode(); mViewModeToggleQueued = false; } } if (paused) return; // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); if(mMode == Mode::Vanity) rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); if (isFirstPerson() && mHeadBobbingEnabled) updateHeadBobbing(duration); else mRoll = mHeadBobbingOffset = 0; updateFocalPointOffset(duration); updatePosition(); float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); mTotalMovement += speed * duration; speed /= (1.f + speed / 500.f); float maxDelta = 300.f * duration; mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); updateStandingPreviewMode(); } void Camera::updatePosition() { mFocalPointAdjustment = osg::Vec3d(); if (isFirstPerson()) return; const float cameraObstacleLimit = 5.0f; const float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); // Adjust focal point to prevent clipping. osg::Vec3d focal = getFocalPoint(); osg::Vec3d focalOffset = getFocalPointOffset(); float offsetLen = focalOffset.length(); if (offsetLen > 0) { MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; mFocalPointAdjustment = focalOffset * std::max(-1.0, adjustmentCoef); } } // Calculate camera distance. mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); if (mDynamicCameraDistanceEnabled) mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); osg::Vec3d cameraPos; getPosition(focal, cameraPos); MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit); if (result.mHit) mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); } void Camera::updateStandingPreviewMode() { if (!mStandingPreviewAllowed) return; float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); bool combat = mTrackingPtr.getClass().isActor() && mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; bool standingStill = speed == 0 && !combat && !mFirstPersonView; if (!standingStill && mMode == Mode::StandingPreview) { mMode = Mode::Normal; calculateDeferredRotation(); } else if (standingStill && mMode == Mode::Normal) mMode = Mode::StandingPreview; } void Camera::setFocalPointTargetOffset(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) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later if (!mAnimation->upperBodyReady() && !force) { mViewModeToggleQueued = true; return; } else mViewModeToggleQueued = false; mFirstPersonView = !mFirstPersonView; updateStandingPreviewMode(); instantTransition(); processViewChange(); } void Camera::allowVanityMode(bool allow) { if (!allow && mMode == Mode::Vanity) { disableDeferredPreviewRotation(); toggleVanityMode(false); } mVanityAllowed = allow; } bool Camera::toggleVanityMode(bool enable) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later if (mFirstPersonView && !mAnimation->upperBodyReady()) { mVanityToggleQueued = true; mVanityToggleQueuedValue = enable; return false; } if (!mVanityAllowed && enable) return false; if ((mMode == Mode::Vanity) == enable) return true; mMode = enable ? Mode::Vanity : Mode::Normal; if (!mDeferredRotationAllowed) disableDeferredPreviewRotation(); if (!enable) calculateDeferredRotation(); processViewChange(); return true; } void Camera::togglePreviewMode(bool enable) { if (mFirstPersonView && !mAnimation->upperBodyReady()) return; if((mMode == Mode::Preview) == enable) return; mMode = enable ? Mode::Preview : Mode::Normal; if (mMode == Mode::Normal) updateStandingPreviewMode(); else if (mFirstPersonView) instantTransition(); if (mMode == Mode::Normal) { if (!mDeferredRotationAllowed) disableDeferredPreviewRotation(); calculateDeferredRotation(); } processViewChange(); } void Camera::setSneakOffset(float offset) { mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } void Camera::setYaw(float angle) { mYaw = Misc::normalizeAngle(angle); } void Camera::setPitch(float angle) { const float epsilon = 0.000001f; float limit = static_cast(osg::PI_2) - epsilon; mPitch = osg::clampBetween(angle, -limit, limit); } float Camera::getCameraDistance() const { if (isFirstPerson()) return 0.f; return mCameraDistance; } void Camera::adjustCameraDistance(float delta) { if (!isFirstPerson()) { if(isNearest() && delta < 0.f && getMode() != Mode::Preview && getMode() != Mode::Vanity) toggleViewMode(); else mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta; } else if (delta > 0.f) { toggleViewMode(); mBaseCameraDistance = 0; } mIsNearest = mBaseCameraDistance <= mNearest; mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest); Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance); } float Camera::getCameraDistanceCorrection() const { if (!mDynamicCameraDistanceEnabled) return 0; float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f; float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed; float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef; return pitchCorrection + speedCorrection; } void Camera::setAnimation(NpcAnimation *anim) { mAnimation = anim; processViewChange(); } void Camera::processViewChange() { if(isFirstPerson()) { 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; } rotateCamera(getPitch(), getYaw(), false); } void Camera::applyDeferredPreviewRotationToPlayer(float dt) { if (isVanityOrPreviewModeEnabled() || 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() { 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() { MWWorld::Ptr ptr = mTrackingPtr; if (isVanityOrPreviewModeEnabled() || 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); } } openmw-openmw-0.47.0/apps/openmw/mwrender/camera.hpp000066400000000000000000000120271413061077700224560ustar00rootroot00000000000000#ifndef GAME_MWRENDER_CAMERA_H #define GAME_MWRENDER_CAMERA_H #include #include #include #include #include "../mwworld/ptr.hpp" namespace osg { class Camera; class NodeCallback; class Node; } namespace MWRender { class NpcAnimation; /// \brief Camera control class Camera { public: enum class Mode { Normal, Vanity, Preview, StandingPreview }; private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; float mHeightScale; osg::ref_ptr mCamera; NpcAnimation *mAnimation; bool mFirstPersonView; Mode mMode; bool mVanityAllowed; bool mStandingPreviewAllowed; bool mDeferredRotationAllowed; float mNearest; float mFurthest; bool mIsNearest; float mHeight, mBaseCameraDistance; float mPitch, mYaw, mRoll; bool mVanityToggleQueued; bool mVanityToggleQueuedValue; bool mViewModeToggleQueued; float mCameraDistance; float mMaxNextCameraDistance; osg::Vec3d mFocalPointAdjustment; 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; float mSmoothedSpeed; float mZoomOutWhenMoveCoef; bool mDynamicCameraDistanceEnabled; bool mShowCrosshairInThirdPersonMode; bool mHeadBobbingEnabled; float mHeadBobbingOffset; float mHeadBobbingWeight; // Value from 0 to 1 for smooth enabling/disabling. float mTotalMovement; // Needed for head bobbing. void updateHeadBobbing(float duration); void updateFocalPointOffset(float duration); void updatePosition(); float getCameraDistanceCorrection() const; osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; bool mDeferredRotationDisabled; void calculateDeferredRotation(); void updateStandingPreviewMode(); public: 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; } void setFocalPointTargetOffset(osg::Vec2d v); void instantTransition(); void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } /// Update the view matrix of \a cam void updateCamera(osg::Camera* cam); /// Reset to defaults void reset(); /// Set where the camera is looking at. Uses Morrowind (euler) angles /// \param rot Rotation angles in radians void rotateCamera(float pitch, float yaw, bool adjust); void rotateCameraToTrackingPtr(); float getYaw() const { return mYaw; } void setYaw(float angle); float getPitch() const { return mPitch; } void setPitch(float angle); /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force=false); bool toggleVanityMode(bool enable); void allowVanityMode(bool allow); /// @note this may be ignored if an important animation is currently playing void togglePreviewMode(bool enable); void applyDeferredPreviewRotationToPlayer(float dt); void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } /// \brief Lowers the camera for sneak. void setSneakOffset(float offset); bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; } void processViewChange(); void update(float duration, bool paused=false); /// Adds distDelta to the camera distance. Switches 3rd/1st person view if distance is less than limit. void adjustCameraDistance(float distDelta); float getCameraDistance() const; void setAnimation(NpcAnimation *anim); osg::Vec3d getFocalPoint() const; osg::Vec3d getFocalPointOffset() const; /// Stores focal and camera world positions in passed arguments void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; } Mode getMode() const { return mMode; } bool isNearest() const { return mIsNearest; } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/cell.hpp000066400000000000000000000017151413061077700221470ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/characterpreview.cpp000066400000000000000000000533211413061077700245610ustar00rootroot00000000000000#include "characterpreview.hpp" #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 osg::NodeCallback { public: DrawOnceCallback () : mRendered(false) , mLastRenderedFrame(0) { } void operator () (osg::Node* node, osg::NodeVisitor* nv) override { 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); 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; }; // 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), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) { } 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->addUniform(mNoAlphaUniform); } } traverse(node); } private: osg::ref_ptr mNoAlphaUniform; }; 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) { mTexture = new osg::Texture2D; mTexture->setTextureSize(sizeX, sizeY); mTexture->setInternalFormat(GL_RGBA); mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mTexture->setUserValue("premultiplied alpha", true); mCamera = new osg::Camera; // hints that the camera is not relative to the master camera mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); const float fovYDegrees = 12.3f; mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setRenderOrder(osg::Camera::PRE_RENDER); mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->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->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); // 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->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); stateset->addUniform(new osg::Uniform("noAlpha", true)); 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); mCamera->addChild(lightManager); mNode = new osg::PositionAttitudeTransform; lightManager->addChild(mNode); mDrawOnceCallback = new DrawOnceCallback; mCamera->addUpdateCallback(mDrawOnceCallback); mParent->addChild(mCamera); mCharacter.mCell = nullptr; } CharacterPreview::~CharacterPreview () { mCamera->removeChildren(0, mCamera->getNumChildren()); mParent->removeChild(mCamera); } int CharacterPreview::getTextureWidth() const { return mSizeX; } int CharacterPreview::getTextureHeight() const { return mSizeY; } void CharacterPreview::setBlendMode() { mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } void CharacterPreview::onSetup() { setBlendMode(); } osg::ref_ptr CharacterPreview::getTexture() { return mTexture; } void CharacterPreview::rebuild() { mAnimation = nullptr; mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); onSetup(); redraw(); } void CharacterPreview::redraw() { mCamera->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); mCamera->setStateSet(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->getTypeName() == typeid(ESM::Weapon).name()) { 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->getTypeName() == typeid(ESM::Light).name() && 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()); osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); mCamera->setNodeMask(~0u); mCamera->accept(visitor); mCamera->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); mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1)); } // -------------------------------------------------------------------------------------------------- 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 osg::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) : mNodeToFollow(nodeToFollow) , mPosOffset(posOffset) , mLookAtOffset(lookAtOffset) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osg::Camera* cam = static_cast(node); // 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(); cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); } 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) mCamera->removeUpdateCallback(mUpdateCameraCallback); const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (head) { mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); mCamera->addUpdateCallback(mUpdateCameraCallback); } else Log(Debug::Error) << "Error: Bip01 Head node not found"; } } openmw-openmw-0.47.0/apps/openmw/mwrender/characterpreview.hpp000066400000000000000000000056671413061077700246000ustar00rootroot00000000000000#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; } namespace MWRender { class NpcAnimation; class DrawOnceCallback; 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(); 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 mTexture; osg::ref_ptr mCamera; osg::ref_ptr mDrawOnceCallback; 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.47.0/apps/openmw/mwrender/creatureanimation.cpp000066400000000000000000000225501413061077700247350ustar00rootroot00000000000000#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::shared_ptr(new WeaponAnimationTime(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.getTypeName() == typeid(ESM::Weapon).name()) { 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(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) bonename = "Weapon Bone"; } } else bonename = "Weapon Bone"; } else { bonename = "Shield Bone"; if (item.getTypeName() == typeid(ESM::Armor).name()) { // Shield body part model should be used if possible. const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (const auto& part : item.get()->mBase->mParts.mParts) { // Assume all creatures use the male mesh. if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) continue; const ESM::BodyPart *bodypart = store.get().search(part.mMale); if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) { itemModel = "meshes\\" + bodypart->mModel; break; } } } } try { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(itemModel); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); scene.reset(new PartHolder(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.getTypeName() == typeid(ESM::Weapon).name() && 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.reset(new NullAnimationTime); 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->getTypeName() != typeid(ESM::Weapon).name()) return nullptr; int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; // 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.47.0/apps/openmw/mwrender/creatureanimation.hpp000066400000000000000000000046651413061077700247510ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/effectmanager.cpp000066400000000000000000000042521413061077700240110ustar00rootroot00000000000000#include "effectmanager.hpp" #include #include #include #include #include "animation.hpp" #include "vismask.hpp" #include "util.hpp" 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.reset(new EffectAnimationTime); 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); SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); node->accept(assignVisitor); if (isMagicVFX) overrideFirstRootTexture(textureOverride, mResourceSystem, node); else overrideTexture(textureOverride, mResourceSystem, node); mParentNode->addChild(trans); mEffects[trans] = effect; } void EffectManager::update(float dt) { for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ) { it->second.mAnimTime->addTime(dt); if (it->second.mAnimTime->getTime() >= it->second.mMaxControllerLength) { mParentNode->removeChild(it->first); mEffects.erase(it++); } else ++it; } } void EffectManager::clear() { for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { mParentNode->removeChild(it->first); } mEffects.clear(); } } openmw-openmw-0.47.0/apps/openmw/mwrender/effectmanager.hpp000066400000000000000000000027721413061077700240230ustar00rootroot00000000000000#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(); /// 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; }; typedef std::map, Effect> EffectMap; EffectMap mEffects; osg::ref_ptr mParentNode; Resource::ResourceSystem* mResourceSystem; EffectManager(const EffectManager&); void operator=(const EffectManager&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/fogmanager.cpp000066400000000000000000000072261413061077700233340ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/fogmanager.hpp000066400000000000000000000016031413061077700233320ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/globalmap.cpp000066400000000000000000000561721413061077700231700ustar00rootroot00000000000000#include "globalmap.hpp" #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" 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 osg::NodeCallback { public: CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) : mRendered(false) , mParent(parent) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { if (mRendered) { if (mParent->copyResult(static_cast(node), nv->getTraversalNumber())) { node->setNodeMask(0); mParent->markForRemoval(static_cast(node)); } return; } traverse(node, nv); mRendered = true; } private: bool mRendered; MWRender::GlobalMap* mParent; }; } 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; }; 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); imageY = 1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1); } void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY) { imageX = float(x - mMinX) / (mMaxX - mMinX + 1); // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1); } 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 osg::Depth; depth->setWriteMask(0); 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; 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; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); if (!result.success()) { Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); return; } std::string data = ostream.str(); map.mImageData = std::vector(data.begin(), data.end()); } 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) { 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); } } openmw-openmw-0.47.0/apps/openmw/mwrender/globalmap.hpp000066400000000000000000000071011413061077700231610ustar00rootroot00000000000000#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; } int getCellSize() const { return mCellSize; } void worldPosToImageSpace(float x, float z, float& imageX, float& imageY); void cellTopLeftCornerToImageSpace(int x, int y, 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(); private: /** * 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; int mWidth; int mHeight; int mMinX, mMaxX, mMinY, mMaxY; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/groundcover.cpp000066400000000000000000000253671413061077700235710ustar00rootroot00000000000000#include "groundcover.hpp" #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" namespace MWRender { std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) { switch (type) { case ESM::REC_STAT: return store.get().searchStatic(id)->mModel; default: return std::string(); } } void GroundcoverUpdater::setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; } void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) { mPlayerPos = playerPos; } void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) { osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); stateset->addUniform(windUniform.get()); osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); stateset->addUniform(playerPosUniform.get()); } void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); if (windUniform != nullptr) windUniform->set(mWindSpeed); osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); if (playerPosUniform != nullptr) playerPosUniform->set(mPlayerPos); } 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::Node& node) override { osg::ref_ptr ss = node.getStateSet(); if (ss != nullptr) { ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); } traverse(node); } 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.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); osg::ref_ptr ss = geom.getOrCreateStateSet(); ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); traverse(geom); } private: std::vector mInstances; osg::Vec3f mChunkPosition; void removeAlpha(osg::StateSet* stateset) { // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); stateset->setRenderBinToInherit(); } }; 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; }; 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()) || (minBound.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) { ChunkId id = std::make_tuple(center, size, activeGrid); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return obj->asNode(); 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) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) { } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); 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); std::vector esm; 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) { const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); if (!cell) continue; calculator.reset(); for (size_t i=0; imContextList.size(); ++i) { unsigned int index = cell->mContextList.at(i).index; if (esm.size() <= index) esm.resize(index+1); cell->restore(esm[index], i); ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; bool deleted = false; while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); std::string model = getGroundcoverModel(type, ref.mRefID, store); if (model.empty()) continue; model = "meshes/" + model; instances[model].emplace_back(std::move(ref), std::move(model)); } } } } } 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_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); mSceneManager->reinstateRemovedState(node); InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } // Force a unified alpha handling instead of data from meshes osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); group->getBound(); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); 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.47.0/apps/openmw/mwrender/groundcover.hpp000066400000000000000000000042221413061077700235610ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_GROUNDCOVER_H #define OPENMW_MWRENDER_GROUNDCOVER_H #include #include #include #include namespace MWRender { class GroundcoverUpdater : public SceneUtil::StateSetUpdater { public: GroundcoverUpdater() : mWindSpeed(0.f) , mPlayerPos(osg::Vec3f()) { } void setWindSpeed(float windSpeed); void setPlayerPos(osg::Vec3f playerPos); protected: void setDefaults(osg::StateSet *stateset) override; void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; private: float mWindSpeed; osg::Vec3f mPlayerPos; }; typedef std::tuple ChunkId; // Center, Size, ActiveGrid class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: Groundcover(Resource::SceneManager* sceneManager, float density); ~Groundcover() = 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; unsigned int getNodeMask() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; struct GroundcoverEntry { ESM::Position mPos; float mScale; std::string mModel; GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) { mPos = ref.mPos; mScale = ref.mScale; mModel = model; } }; private: Resource::SceneManager* mSceneManager; float mDensity; 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.47.0/apps/openmw/mwrender/landmanager.cpp000066400000000000000000000022161413061077700234710ustar00rootroot00000000000000#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 ESM::Land* land = MWBase::Environment::get().getWorld()->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.47.0/apps/openmw/mwrender/landmanager.hpp000066400000000000000000000012231413061077700234730ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/localmap.cpp000066400000000000000000000655771413061077700230330ustar00rootroot00000000000000#include "localmap.hpp" #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 { class CameraLocalUpdateCallback : public osg::NodeCallback { public: CameraLocalUpdateCallback(MWRender::LocalMap* parent) : mRendered(false) , mParent(parent) { } void operator()(osg::Node* node, osg::NodeVisitor*) override { if (mRendered) node->setNodeMask(0); if (!mRendered) { mRendered = true; mParent->markForRemoval(static_cast(node)); } // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, // so it has been updated already. //traverse(node, nv); } private: bool mRendered; MWRender::LocalMap* mParent; }; 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 { 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& camera : mActiveCameras) removeCamera(camera); for (auto& camera : mCamerasPendingRemoval) removeCamera(camera); } 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() { mSegments.clear(); } void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { const MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (segment.mFogOfWarImage && segment.mHasFogState) { std::unique_ptr fog (new ESM::FogState()); fog->mFogTextures.emplace_back(); segment.saveFogOfWar(fog->mFogTextures.back()); cell->setFog(fog.release()); } } else { auto segments = divideIntoSegments(mBounds, mMapWorldSize); std::unique_ptr fog (new ESM::FogState()); 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 = mSegments[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(fog.release()); } } osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) { osg::ref_ptr camera (new osg::Camera); camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); 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); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); camera->setNodeMask(Mask_RenderToTexture); // 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::CullStack::SMALL_FEATURE_CULLING); camera->setCullingMode(cullingMode); osg::ref_ptr stateset = new osg::StateSet; stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); // 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); 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.get()), light, stateset); camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); return camera; } void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int y) { osg::ref_ptr texture (new osg::Texture2D); texture->setTextureSize(mMapResolution, mMapResolution); 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); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); camera->addChild(mSceneRoot); mRoot->addChild(camera); mActiveCameras.push_back(camera); MapSegment& segment = mSegments[std::make_pair(x, y)]; segment.mMapTexture = texture; } bool needUpdate(std::set >& renderedGrid, std::set >& currentGrid, int cellX, int cellY) { // if all the cells of the current grid are contained in the rendered grid then we can keep the old render for (int dx=-1;dx<2;dx+=1) { for (int dy=-1;dy<2;dy+=1) { bool haveInRenderedGrid = renderedGrid.find(std::make_pair(cellX+dx,cellY+dy)) != renderedGrid.end(); bool haveInCurrentGrid = currentGrid.find(std::make_pair(cellX+dx,cellY+dy)) != currentGrid.end(); if (haveInCurrentGrid && !haveInRenderedGrid) return true; } } return false; } void LocalMap::requestMap(const MWWorld::CellStore* cell) { if (cell->isExterior()) { int cellX = cell->getCell()->getGridX(); int cellY = cell->getCell()->getGridY(); MapSegment& segment = mSegments[std::make_pair(cellX, cellY)]; if (!needUpdate(segment.mGrid, mCurrentGrid, cellX, cellY)) return; else { segment.mGrid = mCurrentGrid; requestExteriorMap(cell); } } else requestInteriorMap(cell); } void LocalMap::addCell(MWWorld::CellStore *cell) { if (cell->isExterior()) mCurrentGrid.emplace(cell->getCell()->getGridX(), cell->getCell()->getGridY()); } void LocalMap::removeCell(MWWorld::CellStore *cell) { saveFogOfWar(cell); if (cell->isExterior()) { std::pair coords = std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()); mSegments.erase(coords); mCurrentGrid.erase(coords); } else mSegments.clear(); } osg::ref_ptr LocalMap::getMapTexture(int x, int y) { SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); if (found == mSegments.end()) return osg::ref_ptr(); else return found->second.mMapTexture; } osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) { SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); if (found == mSegments.end()) return osg::ref_ptr(); else return found->second.mFogOfWarTexture; } void LocalMap::removeCamera(osg::Camera *cam) { cam->removeChildren(0, cam->getNumChildren()); mRoot->removeChild(cam); } void LocalMap::markForRemoval(osg::Camera *cam) { CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); if (found == mActiveCameras.end()) { Log(Debug::Error) << "Error: trying to remove an inactive camera"; return; } mActiveCameras.erase(found); mCamerasPendingRemoval.push_back(cam); } void LocalMap::cleanupCameras() { if (mCamerasPendingRemoval.empty()) return; for (auto& camera : mCamerasPendingRemoval) removeCamera(camera); mCamerasPendingRemoval.clear(); } void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) { 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(); osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, osg::Vec3d(0,1,0), zmin, zmax); setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (!segment.mFogOfWarImage) { 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; } mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); mBounds.yMax() = std::max(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; osg::ref_ptr camera = createOrthographicCamera(pos.x(), pos.y(), mMapWorldSize, mMapWorldSize, osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); setupRenderToTexture(camera, x, y); auto coords = std::make_pair(x,y); MapSegment& segment = mSegments[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) { const MapSegment& segment = mSegments[std::make_pair(x, y)]; if (!segment.mFogOfWarImage) return false; nX = std::max(0.f, std::min(1.f, nX)); nY = std::max(0.f, std::min(1.f, nY)); 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; MapSegment& segment = mSegments[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::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); uint32_t val = (uint32_t) (alpha << 24); if ( *data != val) { *data = val; changed = true; } ++data; } } if (changed) { segment.mHasFogState = true; segment.mFogOfWarImage->dirty(); } } } } LocalMap::MapSegment::MapSegment() : mHasFogState(false) { } LocalMap::MapSegment::~MapSegment() { } 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); } 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(); mFogOfWarTexture->setImage(mFogOfWarImage); } 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(); mFogOfWarTexture->setImage(mFogOfWarImage); 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()); } } openmw-openmw-0.47.0/apps/openmw/mwrender/localmap.hpp000066400000000000000000000114231413061077700230150ustar00rootroot00000000000000#ifndef GAME_RENDER_LOCALMAP_H #define GAME_RENDER_LOCALMAP_H #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 { /// /// \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 removeCell (MWWorld::CellStore* cell); osg::ref_ptr getMapTexture (int x, int y); osg::ref_ptr getFogOfWarTexture (int x, int y); void removeCamera(osg::Camera* cam); /** * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. */ void markForRemoval(osg::Camera* cam); /** * 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 > CameraVector; CameraVector mActiveCameras; CameraVector mCamerasPendingRemoval; typedef std::set > Grid; Grid mCurrentGrid; struct MapSegment { MapSegment(); ~MapSegment(); void initFogOfWar(); void loadFogOfWar(const ESM::FogTexture& fog); void saveFogOfWar(ESM::FogTexture& fog) const; void createFogOfWarTexture(); osg::ref_ptr mMapTexture; osg::ref_ptr mFogOfWarTexture; osg::ref_ptr mFogOfWarImage; Grid mGrid; // the grid that was active at the time of rendering this segment bool mHasFogState; }; typedef std::map, MapSegment> SegmentMap; SegmentMap mSegments; 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); void requestInteriorMap(const MWWorld::CellStore* cell); osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); void setupRenderToTexture(osg::ref_ptr camera, int x, int y); bool mInterior; osg::BoundingBox mBounds; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/navmesh.cpp000066400000000000000000000031471413061077700226650ustar00rootroot00000000000000#include "navmesh.hpp" #include "vismask.hpp" #include #include namespace MWRender { NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) , mEnabled(enabled) , mGeneration(0) , mRevision(0) { } NavMesh::~NavMesh() { if (mEnabled) disable(); } bool NavMesh::toggle() { if (mEnabled) disable(); else enable(); return mEnabled; } void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision)) return; mId = id; mGeneration = generation; mRevision = revision; if (mGroup) mRootNode->removeChild(mGroup); mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); if (mGroup) { mGroup->setNodeMask(Mask_Debug); mRootNode->addChild(mGroup); } } void NavMesh::reset() { if (mGroup) { mRootNode->removeChild(mGroup); mGroup = nullptr; } } void NavMesh::enable() { if (mGroup) mRootNode->addChild(mGroup); mEnabled = true; } void NavMesh::disable() { if (mGroup) mRootNode->removeChild(mGroup); mEnabled = false; } } openmw-openmw-0.47.0/apps/openmw/mwrender/navmesh.hpp000066400000000000000000000017361413061077700226740ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H #include #include namespace osg { class Group; class Geometry; } namespace MWRender { class NavMesh { public: NavMesh(const osg::ref_ptr& root, bool enabled); ~NavMesh(); bool toggle(); void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings); void reset(); void enable(); void disable(); bool isEnabled() const { return mEnabled; } private: osg::ref_ptr mRootNode; bool mEnabled; std::size_t mId = std::numeric_limits::max(); std::size_t mGeneration; std::size_t mRevision; osg::ref_ptr mGroup; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/npcanimation.cpp000066400000000000000000001365351413061077700237140ustar00rootroot00000000000000#include "npcanimation.hpp" #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" namespace { std::string getVampireHead(const std::string& race, bool female) { 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 "meshes\\" + bodyPart->mModel; } std::string getShieldBodypartMesh(const std::vector& bodyparts, bool female) { 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 "meshes\\" + bodypart->mModel; } } return std::string(); } } 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; }; // -------------------------------------------------------------------------------- /// Subclass RotateController to add a Z-offset for sneaking in first person mode. /// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. /// @note Must be set on a MatrixTransform. class NeckController : public RotateController { public: NeckController(osg::Node* relativeTo) : RotateController(relativeTo) { } void setOffset(const osg::Vec3f& offset) { mOffset = offset; } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osg::MatrixTransform* transform = static_cast(node); osg::Matrix matrix = transform->getMatrix(); osg::Quat worldOrient = getWorldOrientation(node); osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); transform->setMatrix(matrix); traverse(node,nv); } private: osg::Vec3f mOffset; }; // -------------------------------------------------------------------------------------------------------------- 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() { mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6)); } 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 NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; result.insert(std::make_pair(ESM::PRT_Head, "Head")); result.insert(std::make_pair(ESM::PRT_Hair, "Head")); // note it uses "Head" as attach bone, but "Hair" as filter result.insert(std::make_pair(ESM::PRT_Neck, "Neck")); result.insert(std::make_pair(ESM::PRT_Cuirass, "Chest")); result.insert(std::make_pair(ESM::PRT_Groin, "Groin")); result.insert(std::make_pair(ESM::PRT_Skirt, "Groin")); result.insert(std::make_pair(ESM::PRT_RHand, "Right Hand")); result.insert(std::make_pair(ESM::PRT_LHand, "Left Hand")); result.insert(std::make_pair(ESM::PRT_RWrist, "Right Wrist")); result.insert(std::make_pair(ESM::PRT_LWrist, "Left Wrist")); result.insert(std::make_pair(ESM::PRT_Shield, "Shield Bone")); result.insert(std::make_pair(ESM::PRT_RForearm, "Right Forearm")); result.insert(std::make_pair(ESM::PRT_LForearm, "Left Forearm")); result.insert(std::make_pair(ESM::PRT_RUpperarm, "Right Upper Arm")); result.insert(std::make_pair(ESM::PRT_LUpperarm, "Left Upper Arm")); result.insert(std::make_pair(ESM::PRT_RFoot, "Right Foot")); result.insert(std::make_pair(ESM::PRT_LFoot, "Left Foot")); result.insert(std::make_pair(ESM::PRT_RAnkle, "Right Ankle")); result.insert(std::make_pair(ESM::PRT_LAnkle, "Left Ankle")); result.insert(std::make_pair(ESM::PRT_RKnee, "Right Knee")); result.insert(std::make_pair(ESM::PRT_LKnee, "Left Knee")); result.insert(std::make_pair(ESM::PRT_RLeg, "Right Upper Leg")); result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg")); result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle")); result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle")); result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); // Fallback. The real node name depends on the current weapon type. result.insert(std::make_pair(ESM::PRT_Tail, "Tail")); return result; } 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::shared_ptr(new HeadAnimationTime(mPtr)); mWeaponAnimationTime = std::shared_ptr(new WeaponAnimationTime(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; mViewMode = viewMode; MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change mAmmunition.reset(); rebuild(); setRenderBin(); } /// @brief A RenderBin callback to clear the depth buffer before rendering. class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: DepthClearCallback() { mDepth = new osg::Depth; mDepth->setWriteMask(true); } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override { renderInfo.getState()->applyAttribute(mDepth); glClear(GL_DEPTH_BUFFER_BIT); bin->drawImplementation(renderInfo, previous); } osg::ref_ptr mDepth; }; /// 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)) { 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 = "meshes\\" + bp->mModel; 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 = "meshes\\" + bp->mModel; else Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; } const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale); 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("meshes\\" + mNpc->mModel, 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::getShieldMesh(MWWorld::ConstPtr shield) const { std::string mesh = shield.getClass().getModel(shield); 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()) mesh = getShieldBodypartMesh(bodyparts, !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->getTypeName() == typeid(ESM::Clothing).name()) { 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->getTypeName() == typeid(ESM::Armor).name()) { 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).getTypeName() == typeid(ESM::Light).name()) { const ESM::Light *light = part.get()->mBase; addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, "meshes\\"+light->mModel); 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) addOrReplaceIndividualPart((ESM::PartReferenceType)part, -1, 1, "meshes\\"+bodypart->mModel); } } if (wasArrowAttached) attachArrow(); } PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); 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) { 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->getTypeName() == typeid(ESM::Weapon).name()) { 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(Misc::StringUtils::lowerCase(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); } 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; } } } } else if (type == ESM::PRT_Weapon) src = mWeaponAnimationTime; else src.reset(new NullAnimationTime); 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) addOrReplaceIndividualPart((ESM::PartReferenceType)part.mPart, group, priority, "meshes\\"+bodypart->mModel, 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 NeckController(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->getTypeName() == typeid(ESM::Weapon).name() && 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()) MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } updateHolsteredWeapon(!mShowWeapons); updateQuiver(); } 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->getTypeName() == typeid(ESM::Armor).name()) { const ESM::Armor *armor = iter->get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; if (!bodyparts.empty()) mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); } if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) { if (mesh.empty()) reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); if (iter->getTypeName() == typeid(ESM::Light).name() && 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() { PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; if (!part) return nullptr; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) return nullptr; int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; // 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() { PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; if (!part) return nullptr; return part->getNode(); } Resource::ResourceSystem* NpcAnimation::getResourceSystem() { return mResourceSystem; } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) { // During first auto equip, we don't play any sounds. // Basically we don't want sounds when the actor is first loaded, // the items should appear as if they'd always been equipped. if (isNew) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!magicEffect->mHitSound.empty()) sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); else sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); } if (!magicEffect->mHit.empty()) { const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // Don't play particle VFX unless the effect is new or it should be looping. if (isNew || loop) addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); } } 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.47.0/apps/openmw/mwrender/npcanimation.hpp000066400000000000000000000134741413061077700237150ustar00rootroot00000000000000#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 NeckController; class HeadAnimationTime; class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: void equipmentChanged() override; void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) 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=nullptr); 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); 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 getShieldMesh(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 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.47.0/apps/openmw/mwrender/objectpaging.cpp000066400000000000000000001012661413061077700236610ustar00rootroot00000000000000#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 "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" #include "vismask.hpp" 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 std::string(); } } 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 obj->asNode(); 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; } 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 (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::set mRefnums; }; class AnalyzeVisitor : public osg::NodeVisitor { public: AnalyzeVisitor(osg::Node::NodeMask analyzeMask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCurrentStateSet(nullptr) , mCurrentDistance(0.f) , mAnalyzeMask(analyzeMask) {} typedef std::unordered_map StateSetCounter; struct Result { StateSetCounter mStateSetCounter; unsigned int mNumVerts = 0; }; void apply(osg::Node& node) override { if (!(node.getNodeMask() & mAnalyzeMask)) return; 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; } traverse(node); } void apply(osg::Geometry& geom) override { if (!(geom.getNodeMask() & mAnalyzeMask)) return; 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; osg::Node::NodeMask mAnalyzeMask; }; 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)); 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("object paging debug batches", "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; std::vector esm; 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 { unsigned int index = cell->mContextList[i].index; if (esm.size()<=index) esm.resize(index+1); cell->restore(esm[index], i); ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; bool deleted = false; while(cell->getNextRef(esm[index], ref, deleted)) { 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; } if (ref.mRefNum.fromGroundcoverFile()) 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()) || (minBound.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 (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker") continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player int type = store.findStatic(ref.mRefID); std::string model = getModel(type, ref.mRefID, store); if (model.empty()) continue; model = "meshes/" + model; 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.insert(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::Matrixf matrix; matrix.preMultTranslate(pos - worldCenter); matrix.preMultRotate( 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)) ); matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale)); osg::ref_ptr trans = new osg::MatrixTransform(matrix); trans->setDataVariance(osg::Object::STATIC); 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 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; mSceneManager->shareState(mergeGroup); 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) { 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) { osg::Vec2i min (mCell.x(), mCell.y()); osg::Vec2i max (mCell.x()+1, mCell.y()+1); if (cellPos.x() < min.x()) cellPos.x() = min.x(); if (cellPos.x() > max.x()) cellPos.x() = max.x(); if (cellPos.y() < min.y()) cellPos.y() = min.y(); if (cellPos.y() > max.y()) cellPos.y() = max.y(); } 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::set& 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(refnums->mRefnums.begin(), refnums->mRefnums.end()); } } osg::Vec4i mActiveGrid; std::set& mOutput; }; void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) { GetRefnumsFunctor grf(out); grf.mActiveGrid = activeGrid; mCache->call(grf); } void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); } } openmw-openmw-0.47.0/apps/openmw/mwrender/objectpaging.hpp000066400000000000000000000057601413061077700236700ustar00rootroot00000000000000#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::set &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.47.0/apps/openmw/mwrender/objects.cpp000066400000000000000000000152151413061077700226540ustar00rootroot00000000000000#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, 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) == 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.insert(std::make_pair(ptr, 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.insert(std::make_pair(ptr, 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.insert(std::make_pair(ptr, 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; PtrAnimationMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) { if (mUnrefQueue.get()) mUnrefQueue->push(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 (mUnrefQueue.get()) mUnrefQueue->push(iter->second); if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) { MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); invStore.setInvListener(nullptr, ptr); invStore.setContListener(nullptr); } mObjects.erase(iter++); } else ++iter; } CellMap::iterator cell = mCellSceneNodes.find(store); if(cell != mCellSceneNodes.end()) { cell->second->getParent(0)->removeChild(cell->second); if (mUnrefQueue.get()) mUnrefQueue->push(cell->second); mCellSceneNodes.erase(cell); } } void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { osg::Node* 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); if(iter != mObjects.end()) { osg::ref_ptr anim = iter->second; mObjects.erase(iter); anim->updatePtr(cur); mObjects[cur] = anim; } } Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) { PtrAnimationMap::const_iterator iter = mObjects.find(ptr); 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); if(iter != mObjects.end()) return iter->second; return nullptr; } } openmw-openmw-0.47.0/apps/openmw/mwrender/objects.hpp000066400000000000000000000042561413061077700226640ustar00rootroot00000000000000#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{ typedef std::map > PtrAnimationMap; typedef std::map > CellMap; CellMap mCellSceneNodes; PtrAnimationMap mObjects; osg::ref_ptr mRootNode; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mUnrefQueue; void insertBegin(const MWWorld::Ptr& ptr); public: Objects(Resource::ResourceSystem* resourceSystem, 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.47.0/apps/openmw/mwrender/pathgrid.cpp000066400000000000000000000074251413061077700230310ustar00rootroot00000000000000#include "pathgrid.hpp" #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); 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.47.0/apps/openmw/mwrender/pathgrid.hpp000066400000000000000000000022511413061077700230260ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/recastmesh.cpp000066400000000000000000000051611413061077700233600ustar00rootroot00000000000000#include "recastmesh.hpp" #include #include #include "vismask.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); 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(); continue; } ++it; } for (const auto& tile : tiles) { if (mGroups.count(tile.first)) continue; const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); 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.47.0/apps/openmw/mwrender/recastmesh.hpp000066400000000000000000000017531413061077700233700ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RECASTMESH_H #define OPENMW_MWRENDER_RECASTMESH_H #include #include #include namespace osg { class Group; class Geometry; } 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.47.0/apps/openmw/mwrender/renderbin.hpp000066400000000000000000000010521413061077700231720ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/renderinginterface.hpp000066400000000000000000000004441413061077700250640ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/renderingmanager.cpp000066400000000000000000001510011413061077700245250ustar00rootroot00000000000000#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 "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwgui/loadingscreen.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "sky.hpp" #include "effectmanager.hpp" #include "npcanimation.hpp" #include "vismask.hpp" #include "pathgrid.hpp" #include "camera.hpp" #include "viewovershoulder.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" namespace MWRender { 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()->cacheInstance(*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) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP; 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); // 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()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); 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; if (Settings::Manager::getBool("terrain shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Terrain; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= (Mask_Object|Mask_Static); mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, 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"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; globalDefines["useGPUShader4"] = "0"; 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)); // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); 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.reset(new EffectManager(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.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); if (Settings::Manager::getBool("distant terrain", "Terrain")) { 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); mTerrain.reset(new Terrain::QuadTreeWorld( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); if (Settings::Manager::getBool("object paging", "Terrain")) { mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); static_cast(mTerrain.get())->addChunkManager(mObjectPaging.get()); mResourceSystem->addResourceManager(mObjectPaging.get()); } } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); if (Settings::Manager::getBool("enabled", "Groundcover")) { osg::ref_ptr groundcoverRoot = new osg::Group; groundcoverRoot->setNodeMask(Mask_Groundcover); groundcoverRoot->setName("Groundcover Root"); sceneRoot->addChild(groundcoverRoot); mGroundcoverUpdater = new GroundcoverUpdater; groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); if (chunkSize >= 1.0f) chunkSize = 1.0f; else if (chunkSize >= 0.5f) chunkSize = 0.5f; else if (chunkSize >= 0.25f) chunkSize = 0.25f; else if (chunkSize != 0.125f) chunkSize = 0.125f; float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); // Groundcover it is handled in the same way indifferently from if it is from active grid or from distant cell. // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); mCamera.reset(new Camera(mViewer->getCamera())); if (Settings::Manager::getBool("view over shoulder", "Camera")) mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); mScreenshotManager.reset(new ScreenshotManager(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); mFog.reset(new FogManager()); mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); mSky->setCamera(mViewer->getCamera()); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); mStateUpdater = new StateUpdater; sceneRoot->addUpdateCallback(mStateUpdater); 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()->setCullingMode( cullingMode ); mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); float fov = Settings::Manager::getFloat("field of view", "Camera"); mFieldOfView = std::min(std::max(1.f, fov), 179.f); float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); // 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); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); updateProjectionMatrix(); } 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(); } SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() { return mUnrefQueue.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(workItem); } double RenderingManager::getReferenceTime() const { return mViewer->getFrameStamp()->getReferenceTime(); } osg::Group* 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 needsAdjusting = false; if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); 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 float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); else ambient *= targetBrightnessIncreaseFactor; } } 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) { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(specular); } 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); } 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()); if (mGroundcoverWorld) mGroundcoverWorld->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()); if (mGroundcoverWorld) mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } mWater->removeCell(store); } void RenderingManager::enableTerrain(bool enable) { if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); if (mGroundcoverWorld) mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); if (enabled) mShadowManager->enableOutdoorMode(); else mShadowManager->enableIndoorMode(); } 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) { unsigned int mask = mViewer->getCamera()->getCullMask(); bool enabled = mask&Mask_Scene; enabled = !enabled; if (enabled) mask |= Mask_Scene; else mask &= ~Mask_Scene; mViewer->getCamera()->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(); mUnrefQueue->flush(mWorkQueue.get()); float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); if (!paused) { mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); if (mGroundcoverUpdater) { const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); float windSpeed = mSky->getBaseWindSpeed(); mGroundcoverUpdater->setWindSpeed(windSpeed); mGroundcoverUpdater->setPlayerPos(playerPos); } } updateNavMesh(); updateRecastMesh(); if (mViewOverShoulderController) mViewOverShoulderController->update(); mCamera->update(dt, paused); osg::Vec3d focal, cameraPos; mCamera->getPosition(focal, cameraPos); mCurrentCameraPos = cameraPos; bool isUnderwater = mWater->isUnderwater(cameraPos); mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); setFogColor(mFog->getFogColor(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); } void RenderingManager::setWaterHeight(float height) { mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->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; } unsigned int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); mScreenshotManager->screenshot360(image); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); 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); } 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() { double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); float fov = mFieldOfView; if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); // 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. fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); if (mGroundcoverWorld) { float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } 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(); 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")) { stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getNumItems()); mTerrain->reportStats(frameNumber, stats); } } void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) { 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"); updateProjectionMatrix(); } else if (it->first == "Camera" && it->second == "viewing distance") { mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); if(!Settings::Manager::getBool("use distant fog", "Fog")) mStateUpdater->setFogEnd(mViewDistance); updateProjectionMatrix(); } 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 = static_cast(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); mSceneRoot->removeUpdateCallback(mStateUpdater); mStateUpdater = new StateUpdater; mSceneRoot->addUpdateCallback(mStateUpdater); mStateUpdater->setFogEnd(mViewDistance); updateAmbient(); mViewer->startThreading(); } } } } float RenderingManager::getNearClipDistance() const { return mNearClip; } 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(); } } 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 osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const { mActorsPaths->update(actor, path, halfExtents, 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 { const auto locked = it->second->lockConst(); mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(), locked->getNavMeshRevision(), 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::set &out) { if (mObjectPaging) mObjectPaging->getPagedRefnums(activeGrid, out); } } openmw-openmw-0.47.0/apps/openmw/mwrender/renderingmanager.hpp000066400000000000000000000231261413061077700245400ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H #include #include #include #include #include #include "objects.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 UnrefQueue; } namespace DetourNavigator { struct Navigator; struct Settings; } namespace MWRender { class GroundcoverUpdater; class StateUpdater; class EffectManager; class ScreenshotManager; class FogManager; class SkyManager; class NpcAnimation; class Pathgrid; class Camera; class ViewOverShoulderController; class Water; class TerrainStorage; class LandManager; class NavMesh; class ActorsPaths; class RecastMesh; class ObjectPaging; class Groundcover; 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); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); MWRender::Objects& getObjects() override; Resource::ResourceSystem* getResourceSystem(); SceneUtil::WorkQueue* getWorkQueue(); SceneUtil::UnrefQueue* getUnrefQueue(); Terrain::World* getTerrain(); osg::Uniform* mUniformNear; osg::Uniform* mUniformFar; void preloadCommonAssets(); double getReferenceTime() const; osg::Group* 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); 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; 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; float getTerrainHeightAt(const osg::Vec3f& pos); // camera stuff Camera* getCamera() { return mCamera.get(); } const osg::Vec3f& getCameraPosition() const { return mCurrentCameraPos; } /// temporarily override the field of view with given value. void overrideFieldOfView(float val); /// 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 osg::Vec3f& halfExtents, 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::set &out); private: void updateProjectionMatrix(); void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); void updateThirdPersonViewMode(); void reportStats() const; void updateNavMesh(); void updateRecastMesh(); 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 mGroundcoverUpdater; osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; 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 mGroundcoverWorld; 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 mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; std::unique_ptr mViewOverShoulderController; osg::Vec3f mCurrentCameraPos; osg::ref_ptr mStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; float mNightEyeFactor; float mNearClip; float mViewDistance; bool mFieldOfViewOverridden; float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/rendermode.hpp000066400000000000000000000005321413061077700233500ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/ripplesimulation.cpp000066400000000000000000000174261413061077700246310ustar00rootroot00000000000000#include "ripplesimulation.hpp" #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::shared_ptr(new SceneUtil::FrameTimeSource)); 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 osg::Depth); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); osg::ref_ptr polygonOffset (new osg::PolygonOffset); polygonOffset->setUnits(-1); polygonOffset->setFactor(-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.47.0/apps/openmw/mwrender/ripplesimulation.hpp000066400000000000000000000031351413061077700246260ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/rotatecontroller.cpp000066400000000000000000000024621413061077700246250ustar00rootroot00000000000000#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::operator()(osg::Node *node, osg::NodeVisitor *nv) { if (!mEnabled) { traverse(node, nv); return; } osg::MatrixTransform* transform = static_cast(node); osg::Matrix matrix = transform->getMatrix(); osg::Quat worldOrient = getWorldOrientation(node); osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); matrix.setRotate(orient); transform->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.47.0/apps/openmw/mwrender/rotatecontroller.hpp000066400000000000000000000015071413061077700246310ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H #define OPENMW_MWRENDER_ROTATECONTROLLER_H #include #include 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. /// @note Must be set on a MatrixTransform. class RotateController : public osg::NodeCallback { public: RotateController(osg::Node* relativeTo); void setEnabled(bool enabled); void setRotate(const osg::Quat& rotate); void operator()(osg::Node* node, osg::NodeVisitor* nv) override; protected: osg::Quat getWorldOrientation(osg::Node* node); bool mEnabled; osg::Quat mRotate; osg::Node* mRelativeTo; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/screenshotmanager.cpp000066400000000000000000000307211413061077700247320ustar00rootroot00000000000000#include "screenshotmanager.hpp" #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" 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(); 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; 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; 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 = new osg::StateSet; 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); quad->setStateSet(stateset); quad->setUpdateCallback(nullptr); 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); 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); 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, 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 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->getReflectionCamera()); rttCamera->addChild(mWater->getRefractionCamera()); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderCameraToImage(rttCamera.get(),image,w,h); } } openmw-openmw-0.47.0/apps/openmw/mwrender/screenshotmanager.hpp000066400000000000000000000023161413061077700247360ustar00rootroot00000000000000#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, osg::Matrixd cameraTransform=osg::Matrixd()); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/sky.cpp000066400000000000000000002050131413061077700220260ustar00rootroot00000000000000#include "sky.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 #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "vismask.hpp" #include "renderbin.hpp" namespace { osg::ref_ptr createAlphaTrackingUnlitMaterial() { 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(osg::Material::DIFFUSE); return mat; } osg::ref_ptr createUnlitMaterial() { 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(osg::Material::OFF); return mat; } osg::ref_ptr createTexturedQuad(int numUvSets=1) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-0.5, -0.5, 0)); verts->push_back(osg::Vec3f(-0.5, 0.5, 0)); verts->push_back(osg::Vec3f(0.5, 0.5, 0)); verts->push_back(osg::Vec3f(0.5, -0.5, 0)); geom->setVertexArray(verts); osg::ref_ptr texcoords = new osg::Vec2Array; texcoords->push_back(osg::Vec2f(0, 0)); texcoords->push_back(osg::Vec2f(0, 1)); texcoords->push_back(osg::Vec2f(1, 1)); texcoords->push_back(osg::Vec2f(1, 0)); 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; } } namespace MWRender { class AtmosphereUpdater : public SceneUtil::StateSetUpdater { public: void setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } protected: void setDefaults(osg::StateSet* stateset) override { stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); } private: osg::Vec4f mEmissionColor; }; class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater { public: AtmosphereNightUpdater(Resource::ImageManager* imageManager) { // we just need a texture, its contents don't really matter mTexture = new osg::Texture2D(imageManager->getWarningImage()); } void setFade(const float fade) { mColor.a() = fade; } protected: void setDefaults(osg::StateSet* stateset) override { 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 apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(mColor); } osg::ref_ptr mTexture; osg::Vec4f mColor; }; class CloudUpdater : public SceneUtil::StateSetUpdater { public: CloudUpdater() : mAnimationTimer(0.f) , mOpacity(0.f) { } void setAnimationTimer(float timer) { mAnimationTimer = timer; } void setTexture(osg::ref_ptr texture) { mTexture = texture; } void setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } void setOpacity(float opacity) { mOpacity = opacity; } protected: void setDefaults(osg::StateSet *stateset) override { osg::ref_ptr texmat (new osg::TexMat); stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); // 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, osg::StateAttribute::ON); 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 apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override { osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, -mAnimationTimer, 0.f))); stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttribute(1, 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::TexEnvCombine* texEnvCombine = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); } private: float mAnimationTimer; osg::ref_ptr mTexture; osg::Vec4f mEmissionColor; float mOpacity; }; /// 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() { // 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 CullCallback); } CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) : osg::Transform(copy, copyop) { } META_Node(MWRender, CameraRelativeTransform) const osg::Vec3f& getLastViewPoint() const { return mViewPoint; } bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override { 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 computeBound() const override { return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); } class CullCallback : public osg::NodeCallback { public: void operator() (osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); // 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, nv); cv->getProjectionCullingStack().back().popCurrentMask(); cv->getCurrentCullingSet().popCurrentMask(); } }; private: // viewPoint for the current frame mutable osg::Vec3f mViewPoint; }; class ModVertexAlphaVisitor : public osg::NodeVisitor { public: ModVertexAlphaVisitor(int meshType) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mMeshType(meshType) { } void apply(osg::Drawable& drw) override { osg::Geometry* geom = drw.asGeometry(); if (!geom) return; osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); for (unsigned int i=0; isize(); ++i) { float alpha = 1.f; if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row else if (mMeshType == 1) { 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; } else if (mMeshType == 2) { if (geom->getColorArray()) { osg::Vec4Array* origColors = static_cast(geom->getColorArray()); alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; } else alpha = 1.f; } (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); } geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); } private: int mMeshType; }; /// @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 osg::NodeCallback { public: UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) : mCameraRelativeTransform(cameraRelativeTransform) , mEnabled(true) , mWaterLevel(0.f) { } bool isUnderwater() { osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); return mEnabled && viewPoint.z() < mWaterLevel; } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { if (isUnderwater()) return; traverse(node, nv); } void setEnabled(bool enabled) { mEnabled = enabled; } void setWaterLevel(float waterLevel) { mWaterLevel = 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) : mVisibleMask(visibleMask) { mGeom = createTexturedQuad(numUvSets); mTransform = new osg::PositionAttitudeTransform; mTransform->setNodeMask(mVisibleMask); mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); mTransform->addChild(mGeom); parentNode->addChild(mTransform); } virtual ~CelestialBody() {} virtual void adjustTransparency(const float ratio) = 0; void setVisible(bool visible) { mTransform->setNodeMask(visible ? mVisibleMask : 0); } protected: unsigned int mVisibleMask; static const float mDistance; osg::ref_ptr mTransform; osg::ref_ptr mGeom; }; const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); 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); mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); 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 osg::ref_ptr alphaFunc (new osg::AlphaFunc); alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); // 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, osg::StateAttribute::ON); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); createSunFlash(imageManager); createSunGlare(); } ~Sun() { mTransform->removeUpdateCallback(mUpdater); destroySunFlash(); destroySunGlare(); } void setColor(const osg::Vec4f& color) { mUpdater->mColor.r() = color.r(); mUpdater->mColor.g() = color.g(); mUpdater->mColor.b() = color.b(); } void adjustTransparency(const float ratio) override { mUpdater->mColor.a() = ratio; if (mSunGlareCallback) mSunGlareCallback->setGlareView(ratio); if (mSunFlashCallback) mSunFlashCallback->setGlareView(ratio); } void 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 setGlareTimeOfDayFade(float val) { if (mSunGlareCallback) mSunGlareCallback->setTimeOfDayFade(val); } private: class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback { public: osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } }; /// @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) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); #if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) // 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()); #else osg::ref_ptr queryGeom = oqn->getQueryGeometry(); #endif // 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()); #if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) oqn->setQueryGeometry(queryGeom.release()); #endif osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { osg::ref_ptr depth (new osg::Depth); depth->setFunction(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. depth->setZNear(1.0); depth->setZFar(1.0); depth->setWriteMask(false); queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); } else { queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } oqn->setQueryStateSet(queryStateSet); parent->addChild(oqn); return oqn; } void 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); osg::ref_ptr transform (new osg::PositionAttitudeTransform); const float scale = 2.6f; transform->setScale(osg::Vec3f(scale,scale,scale)); mTransform->addChild(transform); osg::ref_ptr geom = createTexturedQuad(); transform->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); mSunFlashNode = transform; mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); mSunFlashNode->addCullCallback(mSunFlashCallback); } void destroySunFlash() { if (mSunFlashNode) { mSunFlashNode->removeCullCallback(mSunFlashCallback); mSunFlashCallback = nullptr; } } void 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); 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); // 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, osg::StateAttribute::ON); mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); mSunGlareNode = camera; mSunGlareNode->addCullCallback(mSunGlareCallback); mTransform->addChild(camera); } void destroySunGlare() { if (mSunGlareNode) { mSunGlareNode->removeCullCallback(mSunGlareCallback); mSunGlareCallback = nullptr; } } class Updater : public SceneUtil::StateSetUpdater { public: osg::Vec4f mColor; Updater() : mColor(1.f, 1.f, 1.f, 1.f) { } void setDefaults(osg::StateSet* stateset) override { stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); } 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)); } }; class OcclusionCallback : public osg::NodeCallback { public: OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : mOcclusionQueryVisiblePixels(oqnVisible) , mOcclusionQueryTotalPixels(oqnTotal) { } protected: float 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; } private: osg::ref_ptr mOcclusionQueryVisiblePixels; osg::ref_ptr mOcclusionQueryTotalPixels; std::map, float> mLastRatio; }; /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. class SunFlashCallback : public OcclusionCallback { public: SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : OcclusionCallback(oqnVisible, oqnTotal) , mGlareView(1.f) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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); } const float threshold = 0.6; visibleRatio = visibleRatio * (1.f - threshold) + threshold; } float scale = visibleRatio; if (scale == 0.f) { // no traverse return; } else { osg::Matrix modelView = *cv->getModelViewMatrix(); modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); if (stateset) cv->pushStateSet(stateset); cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); traverse(node, nv); 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: 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, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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, osg::StateAttribute::ON); cv->pushStateSet(stateset); traverse(node, nv); 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; }; osg::ref_ptr mUpdater; osg::ref_ptr mSunFlashCallback; osg::ref_ptr mSunFlashNode; osg::ref_ptr mSunGlareCallback; osg::ref_ptr mSunGlareNode; 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::ImageManager& imageManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase::Unspecified) , mUpdater(new Updater(imageManager)) { setPhase(MoonState::Phase::Full); setVisible(true); mGeom->addUpdateCallback(mUpdater); } ~Moon() { mGeom->removeUpdateCallback(mUpdater); } void adjustTransparency(const float ratio) override { mUpdater->mTransparency *= ratio; } void 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 setAtmosphereColor(const osg::Vec4f& color) { mUpdater->mAtmosphereColor = color; } void setColor(const osg::Vec4f& color) { mUpdater->mMoonColor = color; } unsigned int getPhaseInt() const { if (mPhase == MoonState::Phase::New) return 0; else if (mPhase == MoonState::Phase::WaxingCrescent) return 1; else if (mPhase == MoonState::Phase::WaningCrescent) return 1; else if (mPhase == MoonState::Phase::FirstQuarter) return 2; else if (mPhase == MoonState::Phase::ThirdQuarter) return 2; else if (mPhase == MoonState::Phase::WaxingGibbous) return 3; else if (mPhase == MoonState::Phase::WaningGibbous) return 3; else if (mPhase == MoonState::Phase::Full) return 4; return 0; } private: struct Updater : public SceneUtil::StateSetUpdater { Resource::ImageManager& mImageManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; float mShadowBlend; osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; Updater(Resource::ImageManager& imageManager) : 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) { } void setDefaults(osg::StateSet* stateset) override { stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); 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, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); 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, osg::StateAttribute::ON); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { 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(); } }; Type mType; MoonState::Phase mPhase; osg::ref_ptr mUpdater; void 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_"; if (phase == MoonState::Phase::New) textureName += "new"; else if(phase == MoonState::Phase::WaxingCrescent) textureName += "one_wax"; else if(phase == MoonState::Phase::FirstQuarter) textureName += "half_wax"; else if(phase == MoonState::Phase::WaxingGibbous) textureName += "three_wax"; else if(phase == MoonState::Phase::WaningCrescent) textureName += "one_wan"; else if(phase == MoonState::Phase::ThirdQuarter) textureName += "half_wan"; else if(phase == MoonState::Phase::WaningGibbous) textureName += "three_wan"; else if(phase == MoonState::Phase::Full) textureName += "full"; 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"); } }; SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) : mSceneManager(sceneManager) , mCamera(nullptr) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) , mDay(0) , mMonth(0) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) , mStormDirection(0,1,0) , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) , mCloudSpeed(0.0f) , mStarsOpacity(0.0f) , mRemainingTransitionTime(0.0f) , mRainEnabled(false) , mRainSpeed(0) , mRainDiameter(0) , mRainMinHeight(0) , mRainMaxHeight(0) , mRainEntranceSpeed(1) , mRainMaxRaindrops(0) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) , mPrecipitationAlpha(0.f) { osg::ref_ptr skyroot (new CameraRelativeTransform); skyroot->setName("Sky Root"); // Assign empty program to specify we don't want shaders // The shaders generated by the SceneManager can't handle everything we need skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); mRootNode = 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); mRootNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } void SkyManager::create() { assert(!mCreated); mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(0); 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(2); atmosphereNight->accept(modStars); mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); mCloudNode = new osg::PositionAttitudeTransform; mEarlyRenderBinRoot->addChild(mCloudNode); mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); mCloudUpdater = new CloudUpdater; mCloudUpdater->setOpacity(1.f); mCloudMesh->addUpdateCallback(mCloudUpdater); mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); mCloudMesh2->accept(modClouds); mCloudUpdater2 = new CloudUpdater; mCloudUpdater2->setOpacity(0.f); mCloudMesh2->addUpdateCallback(mCloudUpdater2); mCloudMesh2->setNodeMask(0); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); 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; } class RainCounter : public osgParticle::ConstantRateCounter { public: int numParticlesToCreate(double dt) const override { // 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); } }; class RainShooter : public osgParticle::Shooter { public: RainShooter() : mAngle(0.f) { } void shoot(osgParticle::Particle* particle) const override { particle->setVelocity(mVelocity); particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); } void setVelocity(const osg::Vec3f& velocity) { mVelocity = velocity; } void setAngle(float angle) { mAngle = angle; } osg::Object* cloneType() const override { return new RainShooter; } osg::Object* clone(const osg::CopyOp &) const override { return new RainShooter(*this); } private: osg::Vec3f mVelocity; float mAngle; }; // 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(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,0,0,mAlpha)); } // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: SetupVisitor(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: float &mAlpha; }; protected: float &mAlpha; }; void SkyManager::setCamera(osg::Camera *camera) { mCamera = camera; } 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? const float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); } private: float &mAlpha; bool mIsRain; }; 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, osg::StateAttribute::ON); 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, osg::StateAttribute::ON); 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); 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) { osg::Quat quat; quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); mCloudNode->setAttitude(quat); if (mParticleNode) { // Morrowind deliberately rotates the blizzard mesh, so so should we. if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); mParticleNode->setAttitude(quat); } } else mCloudNode->setAttitude(osg::Quat()); // UV Scroll the clouds mCloudAnimationTimer += duration * mCloudSpeed * 0.003; mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); // 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(); mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); 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 (mCurrentParticleEffect != weather.mParticleEffect) { 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::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); mParticleEffect->accept(alphaFaderSetupVisitor); SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; mParticleEffect->accept(disableFreezeOnCullVisitor); 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); } } } 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 (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); mCloudUpdater2->setTexture(cloudTex); } } if (mCloudBlendFactor != weather.mCloudBlendFactor) { mCloudBlendFactor = weather.mCloudBlendFactor; mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); mCloudUpdater2->setOpacity(mCloudBlendFactor); mCloudMesh2->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); mCloudUpdater2->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::sunEnable() { if (!mCreated) return; mSun->setVisible(true); } void SkyManager::sunDisable() { if (!mCreated) return; mSun->setVisible(false); } void SkyManager::setStormDirection(const osg::Vec3f &direction) { mStormDirection = 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.47.0/apps/openmw/mwrender/sky.hpp000066400000000000000000000153541413061077700220420ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_SKY_H #define OPENMW_MWRENDER_SKY_H #include #include #include #include #include namespace osg { class Camera; } namespace osg { class Group; class Node; class Material; class PositionAttitudeTransform; } namespace osgParticle { class ParticleSystem; class BoxPlacer; } namespace Resource { class SceneManager; } namespace MWRender { class AtmosphereUpdater; class AtmosphereNightUpdater; class CloudUpdater; class Sun; class Moon; class RainCounter; class RainShooter; class RainFader; class AlphaFader; class UnderwaterSwitchCallback; 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; }; struct MoonState { enum class Phase { Full = 0, WaningGibbous, ThirdQuarter, WaningCrescent, New, WaxingCrescent, FirstQuarter, WaxingGibbous, Unspecified }; float mRotationFromHorizon; float mRotationFromNorth; Phase mPhase; float mShadowBlend; float mMoonAlpha; }; ///@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); ~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 setStormDirection(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; 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 mCloudUpdater2; osg::ref_ptr mCloudMesh; osg::ref_ptr mCloudMesh2; 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; osg::Vec3f mStormDirection; // 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; float mPrecipitationAlpha; osg::Vec4f mMoonScriptColor; }; } #endif // GAME_RENDER_SKY_H openmw-openmw-0.47.0/apps/openmw/mwrender/terrainstorage.cpp000066400000000000000000000051211413061077700242470ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/terrainstorage.hpp000066400000000000000000000023201413061077700242520ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/util.cpp000066400000000000000000000043261413061077700222010ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/util.hpp000066400000000000000000000017661413061077700222130ustar00rootroot00000000000000#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.47.0/apps/openmw/mwrender/viewovershoulder.cpp000066400000000000000000000113661413061077700246420ustar00rootroot00000000000000#include "viewovershoulder.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" #include "../mwmechanics/drawstate.hpp" namespace MWRender { ViewOverShoulderController::ViewOverShoulderController(Camera* camera) : mCamera(camera), mMode(Mode::RightShoulder), mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")), mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f) { osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); mOverShoulderHorizontalOffset = std::abs(offset.x()); mOverShoulderVerticalOffset = offset.y(); mDefaultShoulderIsRight = offset.x() >= 0; mCamera->enableDynamicCameraDistance(true); mCamera->enableCrosshairInThirdPersonMode(true); mCamera->setFocalPointTargetOffset(offset); } void ViewOverShoulderController::update() { if (mCamera->isFirstPerson()) return; Mode oldMode = mMode; auto ptr = mCamera->getTrackingPtr(); bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing; if (combat && !mCamera->isVanityOrPreviewModeEnabled()) mMode = Mode::Combat; else if (MWBase::Environment::get().getWorld()->isSwimming(ptr)) mMode = Mode::Swimming; else if (oldMode == Mode::Combat || oldMode == Mode::Swimming) mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder)) trySwitchShoulder(); if (oldMode == mMode) return; if (mCamera->getMode() == Camera::Mode::Vanity) // Player doesn't touch controls for a long time. Transition should be very slow. mCamera->setFocalPointTransitionSpeed(0.2f); else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::Normal) // Transition to/from combat mode and we are not it preview mode. Should be fast. mCamera->setFocalPointTransitionSpeed(5.f); else mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed. switch (mMode) { case Mode::RightShoulder: mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); break; case Mode::LeftShoulder: mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); break; case Mode::Combat: case Mode::Swimming: default: mCamera->setFocalPointTargetOffset({0, 15}); } } void ViewOverShoulderController::trySwitchShoulder() { if (mCamera->getMode() != Camera::Mode::Normal) return; const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1)); osg::Vec3d playerPos = mCamera->getFocalPoint() - mCamera->getFocalPointOffset(); MWBase::World* world = MWBase::Environment::get().getWorld(); osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); float rayRight = world->getDistToNearestRayHit( playerPos + sideOffset, orient * osg::Vec3d(1, 0, 0), limitToSwitchBack + 1); float rayLeft = world->getDistToNearestRayHit( playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1); float rayRightForward = world->getDistToNearestRayHit( playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1); float rayLeftForward = world->getDistToNearestRayHit( playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1); float distRight = std::min(rayRight, rayRightForward); float distLeft = std::min(rayLeft, rayLeftForward); if (distLeft < limitToSwitch && distRight > limitToSwitchBack) mMode = Mode::RightShoulder; else if (distRight < limitToSwitch && distLeft > limitToSwitchBack) mMode = Mode::LeftShoulder; else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack) mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; } } openmw-openmw-0.47.0/apps/openmw/mwrender/viewovershoulder.hpp000066400000000000000000000012101413061077700246320ustar00rootroot00000000000000#ifndef VIEWOVERSHOULDER_H #define VIEWOVERSHOULDER_H #include "camera.hpp" namespace MWRender { class ViewOverShoulderController { public: ViewOverShoulderController(Camera* camera); void update(); private: void trySwitchShoulder(); enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming }; Camera* mCamera; Mode mMode; bool mAutoSwitchShoulder; float mOverShoulderHorizontalOffset; float mOverShoulderVerticalOffset; bool mDefaultShoulderIsRight; }; } #endif // VIEWOVERSHOULDER_H openmw-openmw-0.47.0/apps/openmw/mwrender/vismask.hpp000066400000000000000000000051541413061077700227060ustar00rootroot00000000000000#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), }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/water.cpp000066400000000000000000000720621413061077700223500ustar00rootroot00000000000000#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 "../mwworld/cellstore.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 osg::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) : osg::NodeCallback() , mCullPlane(cullPlane) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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, nv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); } private: const osg::Plane* mCullPlane; }; class FlipCallback : public osg::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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, nv); 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 osg::NodeCallback { public: InheritViewPointCallback() {} void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); traverse(node, nv); } }; /// 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 osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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, nv); cv->popModelViewMatrix(); } else traverse(node, nv); } }; 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 osg::Camera { public: Refraction() { unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); setRenderOrder(osg::Camera::PRE_RENDER, 1); setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); osg::Camera::setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); // No need for Update traversal since the scene is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) setUpdateCallback(new NoTraverseCallback); // 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); getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; osg::Camera::addChild(mClipCullNode); mRefractionTexture = new osg::Texture2D; mRefractionTexture->setTextureSize(rttSize, rttSize); mRefractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mRefractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mRefractionTexture->setInternalFormat(GL_RGB); mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture); mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture->setTextureSize(rttSize, rttSize); mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mRefractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setScene(osg::Node* scene) { if (mScene) mClipCullNode->removeChild(mScene); mScene = scene; mClipCullNode->addChild(scene); } void setWaterLevel(float waterLevel) { const float refractionScale = std::min(1.0f,std::max(0.0f, Settings::Manager::getFloat("refraction scale", "Water"))); setViewMatrix(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))); } osg::Texture2D* getRefractionTexture() const { return mRefractionTexture.get(); } osg::Texture2D* getRefractionDepthTexture() const { return mRefractionDepthTexture.get(); } private: osg::ref_ptr mClipCullNode; osg::ref_ptr mRefractionTexture; osg::ref_ptr mRefractionDepthTexture; osg::ref_ptr mScene; }; class Reflection : public osg::Camera { public: Reflection(bool isInterior) { setRenderOrder(osg::Camera::PRE_RENDER); setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); osg::Camera::setName("ReflectionCamera"); setCullCallback(new InheritViewPointCallback); setInterior(isInterior); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); setViewport(0, 0, rttSize, rttSize); // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) setUpdateCallback(new NoTraverseCallback); mReflectionTexture = new osg::Texture2D; mReflectionTexture->setTextureSize(rttSize, rttSize); mReflectionTexture->setInternalFormat(GL_RGB); mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture); // 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); getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); mClipCullNode = new ClipCullNode; osg::Camera::addChild(mClipCullNode); SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setInterior(bool isInterior) { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); 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; setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); } void setWaterLevel(float waterLevel) { setViewMatrix(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); } osg::Texture2D* getReflectionTexture() const { return mReflectionTexture.get(); } private: osg::ref_ptr mReflectionTexture; osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; }; /// 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) , mCullCallback(nullptr) { mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem)); mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setDataVariance(osg::Object::STATIC); 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); 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 (mReflection) { mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); mRefraction = nullptr; } if (Settings::Manager::getBool("shader", "Water")) { mReflection = new Reflection(mInterior); mReflection->setWaterLevel(mTop); mReflection->setScene(mSceneRoot); if (mCullCallback) mReflection->addCullCallback(mCullCallback); mParent->addChild(mReflection); if (Settings::Manager::getBool("refraction", "Water")) { mRefraction = new Refraction; mRefraction->setWaterLevel(mTop); mRefraction->setScene(mSceneRoot); if (mCullCallback) mRefraction->addCullCallback(mCullCallback); mParent->addChild(mRefraction); } createShaderWaterStateSet(mWaterGeom, mReflection, mRefraction); } else createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); updateVisible(); } osg::Camera *Water::getReflectionCamera() { return mReflection; } osg::Camera *Water::getRefractionCamera() { return mRefraction; } 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; int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 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); 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::shared_ptr(new SceneUtil::FrameTimeSource)); 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); } void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) { // use a define map to conditionally compile the shader std::map defineMap; defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); 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 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); osg::ref_ptr shaderStateset = new osg::StateSet; shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); shaderStateset->addUniform(new osg::Uniform("reflectionMap", 1)); shaderStateset->setTextureAttributeAndModes(0, normalMap, osg::StateAttribute::ON); shaderStateset->setTextureAttributeAndModes(1, reflection->getReflectionTexture(), osg::StateAttribute::ON); if (refraction) { shaderStateset->setTextureAttributeAndModes(2, refraction->getRefractionTexture(), osg::StateAttribute::ON); shaderStateset->setTextureAttributeAndModes(3, refraction->getRefractionDepthTexture(), osg::StateAttribute::ON); shaderStateset->addUniform(new osg::Uniform("refractionMap", 2)); shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); shaderStateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); } else { shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON); shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); osg::ref_ptr depth (new osg::Depth); depth->setWriteMask(false); shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); auto method = mResourceSystem->getSceneManager()->getLightingMethod(); if (method == SceneUtil::LightingMethod::SingleUBO) program->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) { updateWaterMaterial(); } Water::~Water() { mParent->removeChild(mWaterNode); if (mReflection) { mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); mRefraction = nullptr; } } void Water::listAssetsToPreload(std::vector &textures) { int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 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); // create a new StateSet to prevent threading issues osg::ref_ptr nodeStateSet (new osg::StateSet); nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); mWaterNode->setStateSet(nodeStateSet); } 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(); } } openmw-openmw-0.47.0/apps/openmw/mwrender/water.hpp000066400000000000000000000062371413061077700223560ustar00rootroot00000000000000#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; } 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; osg::Callback* mCullCallback; 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::Camera *getReflectionCamera(); osg::Camera *getRefractionCamera(); void processChangedSettings(const Settings::CategorySettingVector& settings); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwrender/weaponanimation.cpp000066400000000000000000000165061413061077700244200ustar00rootroot00000000000000#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(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->getTypeName() != typeid(ESM::Weapon).name()) 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->getTypeName() != typeid(ESM::Weapon).name()) 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 std::map >& nodes, std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { mSpineControllers[i] = nullptr; std::map >::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.47.0/apps/openmw/mwrender/weaponanimation.hpp000066400000000000000000000046071413061077700244240ustar00rootroot00000000000000#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(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 std::map >& 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.47.0/apps/openmw/mwscript/000077500000000000000000000000001413061077700205405ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwscript/aiextensions.cpp000066400000000000000000000635341413061077700237700ustar00rootroot00000000000000#include "aiextensions.hpp" #include #include #include #include #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.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 objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); // discard additional arguments (reset), because we have no idea what they mean. 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(); // discard additional arguments (reset), because we have no idea what they mean. 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 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(); // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i(duration), x, y, z); 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 actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string 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(); // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetStore().get().find(cellID); MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); 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); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).getAiSequence().isPackageDone(); runtime.push (value); } }; 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 = runtime[0].mInteger; idleValue = std::min(255, std::max(0, idleValue)); idleList.push_back(idleValue); runtime.pop(); --arg0; } if(arg0) { repeat = runtime[0].mInteger != 0; runtime.pop(); --arg0; } // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i class OpGetAiSetting : public Interpreter::Opcode0 { MWMechanics::CreatureStats::AiSetting mIndex; public: OpGetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified(false)); } }; template class OpModAiSetting : public Interpreter::Opcode0 { MWMechanics::CreatureStats::AiSetting mIndex; public: OpModAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); 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::CreatureStats::AiSetting mIndex; public: OpSetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); 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 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(); // discard additional arguments (reset), because we have no idea what they mean. 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 actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string 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(); // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i class OpGetCurrentAIPackage : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const auto value = static_cast(ptr.getClass().getCreatureStats (ptr).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 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 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 testedTargetId = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); bool targetsAreEqual = false; 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(int(targetsAreEqual)); } }; template class OpStartCombat : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime &runtime) override { MWWorld::Ptr actor = R()(runtime); std::string 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); MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); creatureStats.getAiSequence().stopCombat(); } }; 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(); MWMechanics::AiFace facePackage(x, y); actor.getClass().getCreatureStats(actor).getAiSequence().stack(facePackage, actor); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); interpreter.installSegment3 (Compiler::Ai::opcodeAIActivateExplicit, new OpAiActivate); interpreter.installSegment3 (Compiler::Ai::opcodeAiTravel, new OpAiTravel); interpreter.installSegment3 (Compiler::Ai::opcodeAiTravelExplicit, new OpAiTravel); interpreter.installSegment3 (Compiler::Ai::opcodeAiEscort, new OpAiEscort); interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortExplicit, new OpAiEscort); interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCell, new OpAiEscortCell); interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCellExplicit, new OpAiEscortCell); interpreter.installSegment3 (Compiler::Ai::opcodeAiWander, new OpAiWander); interpreter.installSegment3 (Compiler::Ai::opcodeAiWanderExplicit, new OpAiWander); interpreter.installSegment3 (Compiler::Ai::opcodeAiFollow, new OpAiFollow); interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowExplicit, new OpAiFollow); interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCell, new OpAiFollowCell); interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCellExplicit, new OpAiFollowCell); interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDone, new OpGetAiPackageDone); interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDoneExplicit, new OpGetAiPackageDone); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeGetTarget, new OpGetTarget); interpreter.installSegment5 (Compiler::Ai::opcodeGetTargetExplicit, new OpGetTarget); interpreter.installSegment5 (Compiler::Ai::opcodeStartCombat, new OpStartCombat); interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat); interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat); interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/aiextensions.hpp000066400000000000000000000005271413061077700237660ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/animationextensions.cpp000066400000000000000000000074311413061077700253500ustar00rootroot00000000000000#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 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, 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 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, group, mode, loops + 1, true); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnim, new OpSkipAnim); interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnimExplicit, new OpSkipAnim); interpreter.installSegment3 (Compiler::Animation::opcodePlayAnim, new OpPlayAnim); interpreter.installSegment3 (Compiler::Animation::opcodePlayAnimExplicit, new OpPlayAnim); interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnim, new OpLoopAnim); interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnimExplicit, new OpLoopAnim); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/animationextensions.hpp000066400000000000000000000006021413061077700253460ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/cellextensions.cpp000066400000000000000000000233601413061077700243070ustar00rootroot00000000000000#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 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; 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; 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, new OpCellChanged); interpreter.installSegment5 (Compiler::Cell::opcodeTestCells, new OpTestCells); interpreter.installSegment5 (Compiler::Cell::opcodeTestInteriorCells, new OpTestInteriorCells); interpreter.installSegment5 (Compiler::Cell::opcodeCOC, new OpCOC); interpreter.installSegment5 (Compiler::Cell::opcodeCOE, new OpCOE); interpreter.installSegment5 (Compiler::Cell::opcodeGetInterior, new OpGetInterior); interpreter.installSegment5 (Compiler::Cell::opcodeGetPCCell, new OpGetPCCell); interpreter.installSegment5 (Compiler::Cell::opcodeGetWaterLevel, new OpGetWaterLevel); interpreter.installSegment5 (Compiler::Cell::opcodeSetWaterLevel, new OpSetWaterLevel); interpreter.installSegment5 (Compiler::Cell::opcodeModWaterLevel, new OpModWaterLevel); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/cellextensions.hpp000066400000000000000000000005511413061077700243110ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/compilercontext.cpp000066400000000000000000000063261413061077700244720ustar00rootroot00000000000000#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); } bool CompilerContext::isJournalId (const std::string& name) const { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Dialogue *topic = store.get().search (name); return topic && topic->mType==ESM::Dialogue::Journal; } } openmw-openmw-0.47.0/apps/openmw/mwscript/compilercontext.hpp000066400000000000000000000026031413061077700244710ustar00rootroot00000000000000#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? bool isJournalId (const std::string& name) const override; ///< Does \a name match a journal ID? }; } #endif openmw-openmw-0.47.0/apps/openmw/mwscript/consoleextensions.cpp000066400000000000000000000003471413061077700250320ustar00rootroot00000000000000#include "consoleextensions.hpp" #include namespace MWScript { namespace Console { void installOpcodes (Interpreter::Interpreter& interpreter) { } } } openmw-openmw-0.47.0/apps/openmw/mwscript/consoleextensions.hpp000066400000000000000000000005621413061077700250360ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/containerextensions.cpp000066400000000000000000000553631413061077700253620ustar00rootroot00000000000000#include "containerextensions.hpp" #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 "../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.getTypeName() == typeid(ESM::ItemLevList).name()) { 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 { std::string itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false); 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 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().getTypeName() == typeid(ESM::ItemLevList).name(); 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(), item, count); return; } // Calls to unresolved containers affect the base record if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), 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 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 item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); if (count<0) throw std::runtime_error ("second argument for RemoveItem must be non-negative"); // 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(), item, -count); return; } // Calls to unresolved containers affect the base record instead else if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), 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 item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) break; } if (it == invStore.end()) { it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr); 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::shared_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->getTypeName () != typeid(ESM::Armor).name()) { 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 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); const std::string &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->getTypeName() != typeid(ESM::Weapon).name()) { if (it->getTypeName() == typeid(ESM::Lockpick).name()) { runtime.push(-2); } else if (it->getTypeName() == typeid(ESM::Probe).name()) { 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, new OpAddItem); interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem); interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount); interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount); interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem); interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem); interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip); interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip); interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType); interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType); interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped); interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped); interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem); interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem); interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType); interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/containerextensions.hpp000066400000000000000000000006151413061077700253550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/controlextensions.cpp000066400000000000000000000257121413061077700250530ustar00rootroot00000000000000#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 "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 (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRun, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); //Force Jump interpreter.installSegment5 (Compiler::Control::opcodeClearForceJump, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceJumpExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); interpreter.installSegment5 (Compiler::Control::opcodeForceJump, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); interpreter.installSegment5 (Compiler::Control::opcodeForceJumpExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); //Force MoveJump interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJump, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJumpExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJump, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJumpExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); //Force Sneak interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun); interpreter.installSegment5 (Compiler::Control::opcodeGetForceJump, new OpGetForceJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceJumpExplicit, new OpGetForceJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJump, new OpGetForceMoveJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJumpExplicit, new OpGetForceMoveJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/controlextensions.hpp000066400000000000000000000005631413061077700250550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/dialogueextensions.cpp000066400000000000000000000274511413061077700251660ustar00rootroot00000000000000#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 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 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 faction1 = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string 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 faction1 = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string 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 faction1 = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string 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, new OpJournal); interpreter.installSegment5 (Compiler::Dialogue::opcodeJournalExplicit, new OpJournal); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); interpreter.installSegment3 (Compiler::Dialogue::opcodeChoice,new OpChoice); interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreeting, new OpForceGreeting); interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreetingExplicit, new OpForceGreeting); interpreter.installSegment5 (Compiler::Dialogue::opcodeGoodbye, new OpGoodbye); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputation, new OpGetReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputation, new OpSetReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputation, new OpModReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputationExplicit, new OpSetReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputationExplicit, new OpModReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputationExplicit, new OpGetReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetFactionReaction, new OpSetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/dialogueextensions.hpp000066400000000000000000000005671413061077700251720ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/docs/000077500000000000000000000000001413061077700214705ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwscript/docs/vmformat.txt000066400000000000000000000365701413061077700240770ustar00rootroot00000000000000OpenMW 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 opcodes 0x2000320-0x3ffffff unused openmw-openmw-0.47.0/apps/openmw/mwscript/extensions.cpp000066400000000000000000000026461413061077700234530ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/extensions.hpp000066400000000000000000000005261413061077700234530ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/globalscripts.cpp000066400000000000000000000234761413061077700241300ustar00rootroot00000000000000#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 : public boost::static_visitor { 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 : public boost::static_visitor { const MWWorld::Ptr* operator()(const MWWorld::Ptr &ptr) const { return &ptr; } const MWWorld::Ptr* operator()(const std::pair &pair) const { return nullptr; } }; struct PtrResolvingVisitor : public boost::static_visitor { 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 : public boost::static_visitor { 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; } }; } namespace MWScript { GlobalScriptDesc::GlobalScriptDesc() : mRunning (false) {} const MWWorld::Ptr* GlobalScriptDesc::getPtrIfPresent() const { return boost::apply_visitor(PtrGettingVisitor(), mTarget); } MWWorld::Ptr GlobalScriptDesc::getPtr() { MWWorld::Ptr ptr = boost::apply_visitor(PtrResolvingVisitor(), mTarget); mTarget = ptr; return ptr; } GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) {} void GlobalScripts::addScript (const std::string& name, const MWWorld::Ptr& target) { const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().search(name)) { auto desc = std::make_shared(); MWWorld::Ptr ptr = target; desc->mTarget = ptr; desc->mRunning = true; desc->mLocals.configure (*script); mScripts.insert (std::make_pair(name, 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 (const std::string& name) { const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) iter->second->mRunning = false; } bool GlobalScripts::isRunning (const std::string& 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& iter : mScripts) { ESM::GlobalScript script = boost::apply_visitor (ScriptCreatingVisitor(), iter.second->mTarget); script.mId = iter.first; iter.second->mLocals.write (script.mLocals, iter.first); script.mRunning = iter.second->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 (const std::string& name) { std::string name2 = ::Misc::StringUtils::lowerCase (name); auto iter = mScripts.find (name2); if (iter==mScripts.end()) { const ESM::Script *script = mStore.get().find (name); 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 (const std::string& 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 (boost::apply_visitor (visitor, script.second->mTarget)) script.second->mTarget = updated; } } } openmw-openmw-0.47.0/apps/openmw/mwscript/globalscripts.hpp000066400000000000000000000045251413061077700241270ustar00rootroot00000000000000#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; boost::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 }; class GlobalScripts { const MWWorld::ESMStore& mStore; std::map > mScripts; public: GlobalScripts (const MWWorld::ESMStore& store); void addScript (const std::string& name, const MWWorld::Ptr& target = MWWorld::Ptr()); void removeScript (const std::string& name); bool isRunning (const std::string& 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 (const std::string& 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 (const std::string& 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.47.0/apps/openmw/mwscript/guiextensions.cpp000066400000000000000000000237031413061077700241550ustar00rootroot00000000000000#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 = (runtime.getStringLiteral (runtime[0].mInteger)); ::Misc::StringUtils::lowerCaseInPlace(cell); 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(); MWWorld::Store::iterator it = cells.extBegin(); for (; it != cells.extEnd(); ++it) { std::string name = it->mName; ::Misc::StringUtils::lowerCaseInPlace(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->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, new OpShowDialogue (MWGui::GM_Birth)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableClassMenu, new OpShowDialogue (MWGui::GM_Class)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableNameMenu, new OpShowDialogue (MWGui::GM_Name)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableRaceMenu, new OpShowDialogue (MWGui::GM_Race)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsReviewMenu, new OpShowDialogue (MWGui::GM_Review)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableLevelupMenu, new OpShowDialogue (MWGui::GM_Levelup)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableInventoryMenu, new OpEnableWindow (MWGui::GW_Inventory)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableMagicMenu, new OpEnableWindow (MWGui::GW_Magic)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableMapMenu, new OpEnableWindow (MWGui::GW_Map)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsMenu, new OpEnableWindow (MWGui::GW_Stats)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableRest, new OpEnableRest ()); interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu, new OpShowRestMenu); interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenuExplicit, new OpShowRestMenu); interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed); interpreter.installSegment5 (Compiler::Gui::opcodeToggleFogOfWar, new OpToggleFogOfWar); interpreter.installSegment5 (Compiler::Gui::opcodeToggleFullHelp, new OpToggleFullHelp); interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); interpreter.installSegment5 (Compiler::Gui::opcodeToggleMenus, new OpToggleMenus); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/guiextensions.hpp000066400000000000000000000005441413061077700241600ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/interpretercontext.cpp000066400000000000000000000376531413061077700252320ustar00rootroot00000000000000#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 "../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, const std::string& 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; } 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 (const std::string& name) const { return MWBase::Environment::get().getWorld()->getGlobalInt (name); } int InterpreterContext::getGlobalLong (const std::string& name) const { // a global long is internally a float. return MWBase::Environment::get().getWorld()->getGlobalInt (name); } float InterpreterContext::getGlobalFloat (const std::string& name) const { return MWBase::Environment::get().getWorld()->getGlobalFloat (name); } void InterpreterContext::setGlobalShort (const std::string& name, int value) { MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalLong (const std::string& name, int value) { MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalFloat (const std::string& 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 (const std::string& name) const { MWBase::World *world = MWBase::Environment::get().getWorld(); return world->getGlobalVariableType(name); } std::string InterpreterContext::getActionBinding(const std::string& 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(MWWorld::Ptr ptr, MWWorld::Ptr actor) { std::shared_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 (const std::string& id, const std::string& name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mShorts[findLocalVariableIndex (scriptId, name, 's')]; } int InterpreterContext::getMemberLong (const std::string& id, const std::string& name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')]; } float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')]; } void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, int value, bool global) { std::string scriptId (id); Locals& locals = getMemberLocals (scriptId, global); locals.mShorts[findLocalVariableIndex (scriptId, name, 's')] = value; } void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value, bool global) { std::string scriptId (id); Locals& locals = getMemberLocals (scriptId, global); locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')] = value; } void InterpreterContext::setMemberFloat (const std::string& id, const std::string& 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) { 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.47.0/apps/openmw/mwscript/interpretercontext.hpp000066400000000000000000000116101413061077700252200ustar00rootroot00000000000000#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, const std::string& 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. 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 (const std::string& name) const override; int getGlobalLong (const std::string& name) const override; float getGlobalFloat (const std::string& name) const override; void setGlobalShort (const std::string& name, int value) override; void setGlobalLong (const std::string& name, int value) override; void setGlobalFloat (const std::string& name, float value) override; std::vector getGlobals () const override; char getGlobalType (const std::string& name) const override; std::string getActionBinding(const std::string& 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(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. int getMemberShort (const std::string& id, const std::string& name, bool global) const override; int getMemberLong (const std::string& id, const std::string& name, bool global) const override; float getMemberFloat (const std::string& id, const std::string& name, bool global) const override; void setMemberShort (const std::string& id, const std::string& name, int value, bool global) override; void setMemberLong (const std::string& id, const std::string& name, int value, bool global) override; void setMemberFloat (const std::string& id, const std::string& name, float value, bool global) override; MWWorld::Ptr getReference(bool required=true); ///< 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.47.0/apps/openmw/mwscript/locals.cpp000066400000000000000000000211521413061077700225220ustar00rootroot00000000000000#include "locals.hpp" #include "globalscripts.hpp" #include #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, const std::string &var) { try { ensure (script); const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); return (index != -1); } catch (const Compiler::SourceException&) { return false; } } int Locals::getIntVar(const std::string &script, const std::string &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, const std::string &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, const std::string& 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; try { 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); } } } catch (const Compiler::SourceException&) { } return true; } void Locals::read (const ESM::Locals& locals, const std::string& script) { ensure (script); try { 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 } } } } catch (const Compiler::SourceException&) { } } } openmw-openmw-0.47.0/apps/openmw/mwscript/locals.hpp000066400000000000000000000044631413061077700225350ustar00rootroot00000000000000#ifndef GAME_SCRIPT_LOCALS_H #define GAME_SCRIPT_LOCALS_H #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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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.47.0/apps/openmw/mwscript/miscextensions.cpp000066400000000000000000002104351413061077700243240ustar00rootroot00000000000000#include "miscextensions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.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, const std::string& itemId, int level) { for (auto& levelItem : list->mList) { if (levelItem.mLevel == level && itemId == levelItem.mId) return; } ESM::LevelledListBase::LevelItem item; item.mId = itemId; item.mLevel = level; list->mList.push_back(item); } void removeFromLevList(ESM::LevelledListBase* list, const std::string& 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!)"); runtime.push (static_cast(::Misc::Rng::rollDice(limit))); // [o, limit) } }; template class OpStartScript : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr target = R()(runtime, false); std::string 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 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 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.getTypeName() == typeid(ESM::Door).name() && !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 effect = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().isActor()) { runtime.push(0); return; } char *end; long key = strtol(effect.c_str(), &end, 10); if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId(effect); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); effects += stats.getActiveSpells().getMagicEffects(); if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished()) { MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); effects += store.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 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 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 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 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 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) || stats.getSpells().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(); 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 objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); stats.setLastHitObject(std::string()); } }; template class OpHitAttemptOnMe : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject())); stats.setLastHitAttemptObject(std::string()); } }; 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 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(spellId); return; } if (ptr.getClass().isActor()) { MWMechanics::AiCast castPackage(targetId, spellId, 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(spellId); return; } if (ptr.getClass().isActor()) { MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spellId, 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: "; 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 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 { const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const std::string& 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 { const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const std::string& 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 { const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const std::string& 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 { const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const std::string& 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"); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeMenuMode, new OpMenuMode); interpreter.installSegment5 (Compiler::Misc::opcodeRandom, new OpRandom); interpreter.installSegment5 (Compiler::Misc::opcodeScriptRunning, new OpScriptRunning); interpreter.installSegment5 (Compiler::Misc::opcodeStartScript, new OpStartScript); interpreter.installSegment5 (Compiler::Misc::opcodeStartScriptExplicit, new OpStartScript); interpreter.installSegment5 (Compiler::Misc::opcodeStopScript, new OpStopScript); interpreter.installSegment5 (Compiler::Misc::opcodeGetSecondsPassed, new OpGetSecondsPassed); interpreter.installSegment5 (Compiler::Misc::opcodeEnable, new OpEnable); interpreter.installSegment5 (Compiler::Misc::opcodeEnableExplicit, new OpEnable); interpreter.installSegment5 (Compiler::Misc::opcodeDisable, new OpDisable); interpreter.installSegment5 (Compiler::Misc::opcodeDisableExplicit, new OpDisable); interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabled, new OpGetDisabled); interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabledExplicit, new OpGetDisabled); interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); interpreter.installSegment5 (Compiler::Misc::opcodeOnActivateExplicit, new OpOnActivate); interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); interpreter.installSegment5 (Compiler::Misc::opcodeActivateExplicit, new OpActivate); interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock); interpreter.installSegment3 (Compiler::Misc::opcodeLockExplicit, new OpLock); interpreter.installSegment5 (Compiler::Misc::opcodeUnlock, new OpUnlock); interpreter.installSegment5 (Compiler::Misc::opcodeUnlockExplicit, new OpUnlock); interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionDebug, new OpToggleCollisionDebug); interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionBoxes, new OpToggleCollisionBoxes); interpreter.installSegment5 (Compiler::Misc::opcodeToggleWireframe, new OpToggleWireframe); interpreter.installSegment5 (Compiler::Misc::opcodeFadeIn, new OpFadeIn); interpreter.installSegment5 (Compiler::Misc::opcodeFadeOut, new OpFadeOut); interpreter.installSegment5 (Compiler::Misc::opcodeFadeTo, new OpFadeTo); interpreter.installSegment5 (Compiler::Misc::opcodeTogglePathgrid, new OpTogglePathgrid); interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater); interpreter.installSegment5 (Compiler::Misc::opcodeToggleWorld, new OpToggleWorld); interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); interpreter.installSegment5 (Compiler::Misc::opcodePcForce1stPerson, new OpPcForce1stPerson); interpreter.installSegment5 (Compiler::Misc::opcodePcForce3rdPerson, new OpPcForce3rdPerson); interpreter.installSegment5 (Compiler::Misc::opcodePcGet3rdPerson, new OpPcGet3rdPerson); interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc); interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink); interpreter.installSegment5 (Compiler::Misc::opcodePayFine, new OpPayFine); interpreter.installSegment5 (Compiler::Misc::opcodePayFineThief, new OpPayFineThief); interpreter.installSegment5 (Compiler::Misc::opcodeGoToJail, new OpGoToJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked); interpreter.installSegment5 (Compiler::Misc::opcodeGetLockedExplicit, new OpGetLocked); interpreter.installSegment5 (Compiler::Misc::opcodeGetEffect, new OpGetEffect); interpreter.installSegment5 (Compiler::Misc::opcodeGetEffectExplicit, new OpGetEffect); interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGem, new OpAddSoulGem); interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGemExplicit, new OpAddSoulGem); interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGem, new OpRemoveSoulGem); interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem); interpreter.installSegment5 (Compiler::Misc::opcodeDrop, new OpDrop); interpreter.installSegment5 (Compiler::Misc::opcodeDropExplicit, new OpDrop); interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGem, new OpDropSoulGem); interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGemExplicit, new OpDropSoulGem); interpreter.installSegment5 (Compiler::Misc::opcodeGetAttacked, new OpGetAttacked); interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadied, new OpGetSpellReadied); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime); interpreter.installSegment5 (Compiler::Misc::opcodeSetDelete, new OpSetDelete); interpreter.installSegment5 (Compiler::Misc::opcodeSetDeleteExplicit, new OpSetDelete); interpreter.installSegment5 (Compiler::Misc::opcodeGetSquareRoot, new OpGetSquareRoot); interpreter.installSegment5 (Compiler::Misc::opcodeFall, new OpFall); interpreter.installSegment5 (Compiler::Misc::opcodeFallExplicit, new OpFall); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPc, new OpGetStandingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMe, new OpHitAttemptOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMeExplicit, new OpHitAttemptOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast); interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell); interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); interpreter.installSegment3 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); interpreter.installSegment3 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/miscextensions.hpp000066400000000000000000000004701413061077700243250ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/ref.cpp000066400000000000000000000015271413061077700220250ustar00rootroot00000000000000#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 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.47.0/apps/openmw/mwscript/ref.hpp000066400000000000000000000011461413061077700220270ustar00rootroot00000000000000#ifndef GAME_MWSCRIPT_REF_H #define GAME_MWSCRIPT_REF_H #include #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.47.0/apps/openmw/mwscript/scriptmanagerimp.cpp000066400000000000000000000142141413061077700246130ustar00rootroot00000000000000#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 if (!iter->second.mByteCode.empty() && iter->second.mActive) 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.mActive = false; // don't execute again. } return false; } void ScriptManager::clear() { for (auto& script : mScripts) { script.second.mActive = true; } 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); { ScriptCollection::iterator iter = mScripts.find (name2); if (iter!=mScripts.end()) return iter->second.mLocals; } { std::map::iterator 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()); scanner.scan (parser); std::map::iterator iter = mOtherLocals.emplace(name2, locals).first; return iter->second; } throw std::logic_error ("script " + name + " does not exist"); } GlobalScripts& ScriptManager::getGlobalScripts() { return mGlobalScripts; } } openmw-openmw-0.47.0/apps/openmw/mwscript/scriptmanagerimp.hpp000066400000000000000000000047731413061077700246310ustar00rootroot00000000000000#ifndef GAME_SCRIPT_SCRIPTMANAGER_H #define GAME_SCRIPT_SCRIPTMANAGER_H #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; bool mActive; CompiledScript(const std::vector& code, const Compiler::Locals& locals) { mByteCode = code; mLocals = locals; mActive = true; } }; 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwscript/skyextensions.cpp000066400000000000000000000105311413061077700241720ustar00rootroot00000000000000#include "skyextensions.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.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(); MWBase::Environment::get().getWorld()->changeWeather(region, id); } }; 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::max(0, std::min(127, runtime[0].mInteger))); runtime.pop(); arg0--; } MWBase::Environment::get().getWorld()->modRegion(region, chances); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Sky::opcodeToggleSky, new OpToggleSky); interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonWhite, new OpTurnMoonWhite); interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonRed, new OpTurnMoonRed); interpreter.installSegment5 (Compiler::Sky::opcodeGetMasserPhase, new OpGetMasserPhase); interpreter.installSegment5 (Compiler::Sky::opcodeGetSecundaPhase, new OpGetSecundaPhase); interpreter.installSegment5 (Compiler::Sky::opcodeGetCurrentWeather, new OpGetCurrentWeather); interpreter.installSegment5 (Compiler::Sky::opcodeChangeWeather, new OpChangeWeather); interpreter.installSegment3 (Compiler::Sky::opcodeModRegion, new OpModRegion); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/skyextensions.hpp000066400000000000000000000005431413061077700242010ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/soundextensions.cpp000066400000000000000000000227011413061077700245160ustar00rootroot00000000000000#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 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 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 { bool mLoop; public: OpPlaySound3D (bool loop) : mLoop (loop) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWSound::Type::Sfx, mLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); } }; template class OpPlaySoundVP3D : public Interpreter::Opcode0 { bool mLoop; public: OpPlaySoundVP3D (bool loop) : mLoop (loop) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string 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, mLoop ? 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 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, new OpSay); interpreter.installSegment5 (Compiler::Sound::opcodeSayDone, new OpSayDone); interpreter.installSegment5 (Compiler::Sound::opcodeStreamMusic, new OpStreamMusic); interpreter.installSegment5 (Compiler::Sound::opcodePlaySound, new OpPlaySound); interpreter.installSegment5 (Compiler::Sound::opcodePlaySoundVP, new OpPlaySoundVP); interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3D, new OpPlaySound3D (false)); interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVP, new OpPlaySoundVP3D (false)); interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3D, new OpPlaySound3D (true)); interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVP, new OpPlaySoundVP3D (true)); interpreter.installSegment5 (Compiler::Sound::opcodeStopSound, new OpStopSound); interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlaying, new OpGetSoundPlaying); interpreter.installSegment5 (Compiler::Sound::opcodeSayExplicit, new OpSay); interpreter.installSegment5 (Compiler::Sound::opcodeSayDoneExplicit, new OpSayDone); interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DExplicit, new OpPlaySound3D (false)); interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVPExplicit, new OpPlaySoundVP3D (false)); interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DExplicit, new OpPlaySound3D (true)); interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVPExplicit, new OpPlaySoundVP3D (true)); interpreter.installSegment5 (Compiler::Sound::opcodeStopSoundExplicit, new OpStopSound); interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlayingExplicit, new OpGetSoundPlaying); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/soundextensions.hpp000066400000000000000000000005501413061077700245210ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/statsextensions.cpp000066400000000000000000001704261413061077700245340ustar00rootroot00000000000000#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(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; } } 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); 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); if (value == 0) return; if (((attribute.getBase() <= 0) && (value < 0)) || ((attribute.getBase() >= 100) && (value > 0))) return; if (value < 0) attribute.setBase(std::max(0.f, attribute.getBase() + value)); else attribute.setBase(std::min(100.f, attribute.getBase() + 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.setModified (value, 0); stat.setCurrent(value); 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); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); stat.setModified (diff + stat.getModified(), 0); stat.setCurrentModified (diff + stat.getCurrentModified()); stat.setCurrent (diff + current); ptr.getClass().getCreatureStats (ptr).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); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); Interpreter::Type_Float value = 0; Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified(); if (max>0) value = stats.getDynamic(mIndex).getCurrent() / max; runtime.push (value); } }; 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); } }; 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); if (value == 0) return; if (((skill.getBase() <= 0.f) && (value < 0.f)) || ((skill.getBase() >= 100.f) && (value > 0.f))) return; if (value < 0) skill.setBase(std::max(0.f, skill.getBase() + value)); else skill.setBase(std::min(100.f, skill.getBase() + 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(id); ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { // 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); // The spell may have an instant effect which must be handled before the spell's removal. for (const auto& effect : creatureStats.getSpells().getMagicEffects()) { if (effect.second.getMagnitude() <= 0) continue; MWMechanics::CastSpell cast(ptr, ptr); if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude())) creatureStats.getSpells().purgeEffect(effect.first.mId); } MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id); 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 spellid = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(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(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 race = runtime.getStringLiteral(runtime[0].mInteger); ::Misc::StringUtils::lowerCaseInPlace(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; ::Misc::StringUtils::lowerCaseInPlace(npcRace); runtime.push (npcRace == race); } }; 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); MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(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.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 (i)); interpreter.installSegment5 (Compiler::Stats::opcodeGetAttributeExplicit+i, new OpGetAttribute (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetAttribute+i, new OpSetAttribute (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetAttributeExplicit+i, new OpSetAttribute (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModAttribute+i, new OpModAttribute (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModAttributeExplicit+i, new OpModAttribute (i)); } for (int i=0; i (i)); interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicExplicit+i, new OpGetDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamic+i, new OpSetDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamicExplicit+i, new OpSetDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModDynamic+i, new OpModDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModDynamicExplicit+i, new OpModDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamic+i, new OpModCurrentDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamicExplicit+i, new OpModCurrentDynamic (i)); interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatio+i, new OpGetDynamicGetRatio (i)); interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatioExplicit+i, new OpGetDynamicGetRatio (i)); } for (int i=0; i (i)); interpreter.installSegment5 (Compiler::Stats::opcodeGetSkillExplicit+i, new OpGetSkill (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetSkill+i, new OpSetSkill (i)); interpreter.installSegment5 (Compiler::Stats::opcodeSetSkillExplicit+i, new OpSetSkill (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModSkill+i, new OpModSkill (i)); interpreter.installSegment5 (Compiler::Stats::opcodeModSkillExplicit+i, new OpModSkill (i)); } interpreter.installSegment5 (Compiler::Stats::opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel); interpreter.installSegment5 (Compiler::Stats::opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel); interpreter.installSegment5 (Compiler::Stats::opcodeModPCCrimeLevel, new OpModPCCrimeLevel); interpreter.installSegment5 (Compiler::Stats::opcodeAddSpell, new OpAddSpell); interpreter.installSegment5 (Compiler::Stats::opcodeAddSpellExplicit, new OpAddSpell); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpell, new OpRemoveSpell); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit, new OpRemoveSpell); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit, new OpRemoveSpellEffects); interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect); interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit, new OpResurrect); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit, new OpRemoveEffects); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank); interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank); interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction); interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRankExplicit,new OpPCRaiseRank); interpreter.installSegment3(Compiler::Stats::opcodePCLowerRankExplicit,new OpPCLowerRank); interpreter.installSegment3(Compiler::Stats::opcodePCJoinFactionExplicit,new OpPCJoinFaction); interpreter.installSegment3(Compiler::Stats::opcodeGetPCRank,new OpGetPCRank); interpreter.installSegment3(Compiler::Stats::opcodeGetPCRankExplicit,new OpGetPCRank); interpreter.installSegment5(Compiler::Stats::opcodeModDisposition,new OpModDisposition); interpreter.installSegment5(Compiler::Stats::opcodeModDispositionExplicit,new OpModDisposition); interpreter.installSegment5(Compiler::Stats::opcodeSetDisposition,new OpSetDisposition); interpreter.installSegment5(Compiler::Stats::opcodeSetDispositionExplicit,new OpSetDisposition); interpreter.installSegment5(Compiler::Stats::opcodeGetDisposition,new OpGetDisposition); interpreter.installSegment5(Compiler::Stats::opcodeGetDispositionExplicit,new OpGetDisposition); interpreter.installSegment5 (Compiler::Stats::opcodeGetLevel, new OpGetLevel); interpreter.installSegment5 (Compiler::Stats::opcodeGetLevelExplicit, new OpGetLevel); interpreter.installSegment5 (Compiler::Stats::opcodeSetLevel, new OpSetLevel); interpreter.installSegment5 (Compiler::Stats::opcodeSetLevelExplicit, new OpSetLevel); interpreter.installSegment5 (Compiler::Stats::opcodeGetDeadCount, new OpGetDeadCount); interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRep, new OpGetPCFacRep); interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRepExplicit, new OpGetPCFacRep); interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRep, new OpSetPCFacRep); interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRepExplicit, new OpSetPCFacRep); interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRep, new OpModPCFacRep); interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRepExplicit, new OpModPCFacRep); interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDisease, new OpGetCommonDisease); interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDiseaseExplicit, new OpGetCommonDisease); interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDisease, new OpGetBlightDisease); interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDiseaseExplicit, new OpGetBlightDisease); interpreter.installSegment5 (Compiler::Stats::opcodeGetRace, new OpGetRace); interpreter.installSegment5 (Compiler::Stats::opcodeGetRaceExplicit, new OpGetRace); interpreter.installSegment5 (Compiler::Stats::opcodeGetWerewolfKills, new OpGetWerewolfKills); interpreter.installSegment3 (Compiler::Stats::opcodePcExpelled, new OpPcExpelled); interpreter.installSegment3 (Compiler::Stats::opcodePcExpelledExplicit, new OpPcExpelled); interpreter.installSegment3 (Compiler::Stats::opcodePcExpell, new OpPcExpell); interpreter.installSegment3 (Compiler::Stats::opcodePcExpellExplicit, new OpPcExpell); interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelled, new OpPcClearExpelled); interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelledExplicit, new OpPcClearExpelled); interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRank, new OpRaiseRank); interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRankExplicit, new OpRaiseRank); interpreter.installSegment5 (Compiler::Stats::opcodeLowerRank, new OpLowerRank); interpreter.installSegment5 (Compiler::Stats::opcodeLowerRankExplicit, new OpLowerRank); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolf, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolfExplicit, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); interpreter.installSegment5 (Compiler::Stats::opcodeGetStat, new OpGetStat); interpreter.installSegment5 (Compiler::Stats::opcodeGetStatExplicit, new OpGetStat); 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, new OpGetMagicEffect (positive, negative)); interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffectExplicit+i, new OpGetMagicEffect (positive, negative)); interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffect+i, new OpSetMagicEffect (positive, negative)); interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffectExplicit+i, new OpSetMagicEffect (positive, negative)); interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffect+i, new OpModMagicEffect (positive, negative)); interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffectExplicit+i, new OpModMagicEffect (positive, negative)); } } } } openmw-openmw-0.47.0/apps/openmw/mwscript/statsextensions.hpp000066400000000000000000000006001413061077700245230ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/transformationextensions.cpp000066400000000000000000001105151413061077700264350ustar00rootroot00000000000000#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 "../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, false); } template class OpGetDistance : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr from = R()(runtime); std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); 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 '" + 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 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,angle,ay,az,MWBase::RotationFlag_inverseOrder); else if (axis == "y") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_inverseOrder); else if (axis == "z") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_inverseOrder); else if (axis == "u") MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_none); else if (axis == "w") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_none); else if (axis == "v") MWBase::Environment::get().getWorld()->rotateObject(ptr,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 axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); if (axis == "x") { runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0])); } else if (axis == "y") { runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1])); } else if (axis == "z") { runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2])); } } }; template class OpGetAngle : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); if (axis=="x") { runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0])); } else if (axis=="y") { runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1])); } else if (axis=="z") { runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2])); } } }; template class OpGetPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); if(axis == "x") { runtime.push(ptr.getRefData().getPosition().pos[0]); } else if(axis == "y") { runtime.push(ptr.getRefData().getPosition().pos[1]); } else if(axis == "z") { runtime.push(ptr.getRefData().getPosition().pos[2]); } } }; template class OpSetPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.isInCell()) return; std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; runtime.pop(); // 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, true)); } }; template class OpGetStartingPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); if(axis == "x") { runtime.push(ptr.getCellRef().getPosition().pos[0]); } else if(axis == "y") { runtime.push(ptr.getCellRef().getPosition().pos[1]); } else if(axis == "z") { runtime.push(ptr.getCellRef().getPosition().pos[2]); } } }; template class OpPositionCell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (ptr.getContainerStore()) return; if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } 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(); MWWorld::CellStore* store = nullptr; try { store = MWBase::Environment::get().getWorld()->getInterior(cellID); } catch(std::exception&) { // cell not found, move to exterior instead (vanilla PositionCell compatibility) const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); if(!cell) { std::string error = "Warning: PositionCell: unknown interior cell (" + cellID + "), moving to exterior instead"; runtime.getContext().report (error); Log(Debug::Warning) << error; } } if(store) { MWWorld::Ptr base = ptr; ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); float ax = ptr.getRefData().getPosition().rot[0]; float ay = ptr.getRefData().getPosition().rot[1]; // 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(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } } }; template class OpPosition : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.isInCell()) return; if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } 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(); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); // 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 (ptr == MWMechanics::getPlayer()) { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); } else { ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); float ax = ptr.getRefData().getPosition().rot[0]; float ay = ptr.getRefData().getPosition().rot[1]; // 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(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } }; class OpPlaceItemCell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string 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); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); 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().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 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()) { int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); } 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().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 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); 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 axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); float ax = ptr.getRefData().getPosition().rot[0]; float ay = ptr.getRefData().getPosition().rot[1]; float az = ptr.getRefData().getPosition().rot[2]; if (axis == "x") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); else if (axis == "y") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); else if (axis == "z") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); } }; template class OpRotateWorld : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string 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; float xr = ptr.getCellRef().getPosition().rot[0]; float yr = ptr.getCellRef().getPosition().rot[1]; float zr = ptr.getCellRef().getPosition().rot[2]; MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); } }; 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 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, true)); } }; template class OpMoveWorld : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.isInCell()) return; std::string 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, true)); } }; 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, new OpGetDistance); interpreter.installSegment5(Compiler::Transformation::opcodeGetDistanceExplicit, new OpGetDistance); interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); interpreter.installSegment5(Compiler::Transformation::opcodeSetScaleExplicit,new OpSetScale); interpreter.installSegment5(Compiler::Transformation::opcodeSetAngle,new OpSetAngle); interpreter.installSegment5(Compiler::Transformation::opcodeSetAngleExplicit,new OpSetAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetScale,new OpGetScale); interpreter.installSegment5(Compiler::Transformation::opcodeGetScaleExplicit,new OpGetScale); interpreter.installSegment5(Compiler::Transformation::opcodeGetAngle,new OpGetAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetAngleExplicit,new OpGetAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetPos,new OpGetPos); interpreter.installSegment5(Compiler::Transformation::opcodeGetPosExplicit,new OpGetPos); interpreter.installSegment5(Compiler::Transformation::opcodeSetPos,new OpSetPos); interpreter.installSegment5(Compiler::Transformation::opcodeSetPosExplicit,new OpSetPos); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPos,new OpGetStartingPos); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPosExplicit,new OpGetStartingPos); interpreter.installSegment5(Compiler::Transformation::opcodePosition,new OpPosition); interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate); interpreter.installSegment5(Compiler::Transformation::opcodeRotateExplicit,new OpRotate); interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorld,new OpRotateWorld); interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorldExplicit,new OpRotateWorld); interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStart,new OpSetAtStart); interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStartExplicit,new OpSetAtStart); interpreter.installSegment5(Compiler::Transformation::opcodeMove,new OpMove); interpreter.installSegment5(Compiler::Transformation::opcodeMoveExplicit,new OpMove); interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorld,new OpMoveWorld); interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/transformationextensions.hpp000066400000000000000000000006331413061077700264410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwscript/userextensions.cpp000066400000000000000000000045711413061077700243510ustar00rootroot00000000000000#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, new OpUser1); interpreter.installSegment5 (Compiler::User::opcodeUser2, new OpUser2); interpreter.installSegment5 (Compiler::User::opcodeUser3, new OpUser3); interpreter.installSegment5 (Compiler::User::opcodeUser3Explicit, new OpUser3); interpreter.installSegment5 (Compiler::User::opcodeUser4, new OpUser4); interpreter.installSegment5 (Compiler::User::opcodeUser4Explicit, new OpUser4); } } } openmw-openmw-0.47.0/apps/openmw/mwscript/userextensions.hpp000066400000000000000000000005631413061077700243530ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/000077500000000000000000000000001413061077700203645ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwsound/alext.h000066400000000000000000000455241413061077700216640ustar00rootroot00000000000000/** * 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.47.0/apps/openmw/mwsound/efx-presets.h000066400000000000000000001052301413061077700230030ustar00rootroot00000000000000/* 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.47.0/apps/openmw/mwsound/efx.h000066400000000000000000001022601413061077700213200ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/ffmpeg_decoder.cpp000066400000000000000000000333651413061077700240330ustar00rootroot00000000000000#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); return stream.gcount(); } catch (std::exception& ) { return 0; } } 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.47.0/apps/openmw/mwsound/ffmpeg_decoder.hpp000066400000000000000000000043671413061077700240400ustar00rootroot00000000000000#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 #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.47.0/apps/openmw/mwsound/loudness.cpp000066400000000000000000000041331413061077700227250ustar00rootroot00000000000000#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::max(-1.f, std::min(1.f, value)); // 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 = static_cast(sec * mSamplesPerSec); index = std::max(0, std::min(index, mSamples.size()-1)); return mSamples[index]; } } openmw-openmw-0.47.0/apps/openmw/mwsound/loudness.hpp000066400000000000000000000035141413061077700227340ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/movieaudiofactory.cpp000066400000000000000000000136171413061077700246310ustar00rootroot00000000000000#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.reset(new MWSoundDecoderBridge(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::shared_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) { std::shared_ptr decoder(new MWSound::MovieAudioDecoder(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.47.0/apps/openmw/mwsound/movieaudiofactory.hpp000066400000000000000000000005461413061077700246330ustar00rootroot00000000000000#ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H #define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H #include namespace MWSound { class MovieAudioFactory : public Video::MovieAudioFactory { std::shared_ptr createDecoder(Video::VideoState* videoState) override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwsound/openal_output.cpp000066400000000000000000001323131413061077700237710ustar00rootroot00000000000000#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.reset(new Sound_Loudness(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(); // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. if(decoder->mResourceMgr->exists(fname)) decoder->open(fname); else { std::string file = fname; std::string::size_type pos = file.rfind('.'); if(pos != std::string::npos) file = file.substr(0, pos)+".mp3"; decoder->open(file); } 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, bool is3d) { if(is3d) { if((pos - mListenerPos).length2() > maxdist*maxdist) gain = 0.0f; } 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(), sound->getPitch(), 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(), sound->getPitch(), 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(), sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); 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(), sound->getPitch(), 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(), sound->getPitch(), 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(), sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); 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(new StreamThread) { } OpenAL_Output::~OpenAL_Output() { OpenAL_Output::deinit(); } } openmw-openmw-0.47.0/apps/openmw/mwsound/openal_output.hpp000066400000000000000000000065361413061077700240050ustar00rootroot00000000000000#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 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, bool is3d); 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.47.0/apps/openmw/mwsound/regionsoundselector.cpp000066400000000000000000000042461413061077700251730ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/regionsoundselector.hpp000066400000000000000000000013261413061077700251740ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/sound.hpp000066400000000000000000000060671413061077700222360ustar00rootroot00000000000000#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_3D = 1 << 31, }; // 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; float mBaseVolume = 1; float mPitch = 1; float mMinDistance = 1; float mMaxDistance = 1000; int mFlags = 0; float mFadeOutTime = 0; }; 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) { mParams.mFadeOutTime = duration; } void updateFade(float duration) { if (mParams.mFadeOutTime > 0.0f) { float soundDuration = std::min(duration, mParams.mFadeOutTime); mParams.mVolume *= (mParams.mFadeOutTime - soundDuration) / mParams.mFadeOutTime; mParams.mFadeOutTime -= soundDuration; } } const osg::Vec3f &getPosition() const { return mParams.mPos; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume; } 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; } 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() { } }; class Stream : public SoundBase { Stream& operator=(const Stream&) = delete; Stream(const Stream&) = delete; Stream(Stream&&) = delete; public: Stream() { } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwsound/sound_buffer.cpp000066400000000000000000000116471413061077700235620ustar00rootroot00000000000000#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); 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.47.0/apps/openmw/mwsound/sound_buffer.hpp000066400000000000000000000057541413061077700235710ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/sound_decoder.hpp000066400000000000000000000026711413061077700237200ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/sound_output.hpp000066400000000000000000000055751413061077700236610ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/soundmanagerimp.cpp000066400000000000000000001152201413061077700242620ustar00rootroot00000000000000#include "soundmanagerimp.hpp" #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; 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; } } // 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(); // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. if(mVFS->exists(voicefile)) decoder->open(voicefile); else { std::string file = voicefile; std::string::size_type pos = file.rfind('.'); if(pos != std::string::npos) file = file.substr(0, pos)+".mp3"; decoder->open(file); } 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(); decoder->open(filename); mMusic = getStreamRef(); mMusic->init([&] { SoundParams params; params.mBaseVolume = volumeFromType(Type::Music); params.mFlags = PlayMode::NoEnv | 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; const std::map& index = mVFS->getIndex(); std::string pattern = "Music/" + playlist; mVFS->normalizeFilename(pattern); std::map::const_iterator found = index.lower_bound(pattern); while (found != index.end()) { if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) filelist.push_back(found->first); else break; ++found; } 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; const std::map& index = mVFS->getIndex(); // Is there an ini setting for this filename or something? std::string filename = "music/special/morrowind title.mp3"; auto found = index.find(filename); if (found != index.end()) { filelist.emplace_back(found->first); 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; std::string voicefile = "Sound/"+filename; mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); 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, std::move(sound)); } float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { Stream *sound = snditer->second.get(); return mOutput->getStreamLoudness(sound); } return 0.0f; } void SoundManager::say(const std::string& filename) { if(!mOutput->isInitialized()) return; std::string voicefile = "Sound/"+filename; mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); if (!decoder) return; stopSay(MWWorld::ConstPtr()); StreamPtr sound = playVoice(decoder, osg::Vec3f(), true); if(!sound) return; mActiveSaySounds.emplace(MWWorld::ConstPtr(), std::move(sound)); } bool SoundManager::sayDone(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { if(mOutput->isStreamPlaying(snditer->second.get())) return false; return true; } return true; } bool SoundManager::sayActive(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mSaySoundsQueue.find(ptr); if(snditer != mSaySoundsQueue.end()) { if(mOutput->isStreamPlaying(snditer->second.get())) return true; return false; } snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { if(mOutput->isStreamPlaying(snditer->second.get())) return true; return false; } return false; } void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) { SaySoundMap::iterator snditer = mSaySoundsQueue.find(ptr); if(snditer != mSaySoundsQueue.end()) { mOutput->finishStream(snditer->second.get()); mSaySoundsQueue.erase(snditer); } snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { mOutput->finishStream(snditer->second.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::NoEnv | 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(const std::string& 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[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } Sound *SoundManager::playSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { if(!mOutput->isInitialized()) return nullptr; const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3()); if ((mode & PlayMode::RemoveAtDistance) && (mListenerPos - objpos).length2() > 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.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(); mActiveSounds[ptr].emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } Sound *SoundManager::playSound3D(const osg::Vec3f& initialPos, const std::string& 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; SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; params.mPos = initialPos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); 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[MWWorld::ConstPtr()].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); if(snditer != mActiveSounds.end()) { for(SoundBufferRefPair &snd : snditer->second) { if(snd.second == sfx) mOutput->finishSound(snd.first.get()); } } } void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& 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); if(snditer != mActiveSounds.end()) { for(SoundBufferRefPair &snd : snditer->second) mOutput->finishSound(snd.first.get()); } SaySoundMap::iterator sayiter = mSaySoundsQueue.find(ptr); if(sayiter != mSaySoundsQueue.end()) mOutput->finishStream(sayiter->second.get()); sayiter = mActiveSaySounds.find(ptr); if(sayiter != mActiveSaySounds.end()) mOutput->finishStream(sayiter->second.get()); } void SoundManager::stopSound(const MWWorld::CellStore *cell) { for(SoundMap::value_type &snd : mActiveSounds) { if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) { for(SoundBufferRefPair &sndbuf : snd.second) mOutput->finishSound(sndbuf.first.get()); } } for(SaySoundMap::value_type &snd : mSaySoundsQueue) { if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) mOutput->finishStream(snd.second.get()); } for(SaySoundMap::value_type &snd : mActiveSaySounds) { if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) mOutput->finishStream(snd.second.get()); } } void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, float duration) { SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (sfx == nullptr) return; for(SoundBufferRefPair &sndbuf : snditer->second) { if(sndbuf.second == sfx) sndbuf.first->setFadeout(duration); } } } bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr &ptr, const std::string& soundId) const { SoundMap::const_iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); return std::find_if(snditer->second.cbegin(), snditer->second.cend(), [this,sfx](const SoundBufferRefPair &snd) -> bool { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } ) != snditer->second.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()); break; case WaterSoundAction::FinishSound: mOutput->finishSound(mNearWaterSound); mNearWaterSound = nullptr; 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(MWWorld::ConstPtr()); if (snditer != mActiveSounds.end()) { const auto pairiter = std::find_if( snditer->second.begin(), snditer->second.end(), [this](const SoundBufferRefPairList::value_type &item) -> bool { return mNearWaterSound == item.first.get(); } ); if (pairiter != snditer->second.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::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.begin(); while(sndidx != snditer->second.end()) { Sound *sound = sndidx->first.get(); if(!ptr.isEmpty() && sound->getIs3D()) { const ESM::Position &pos = ptr.getRefData().getPosition(); const osg::Vec3f objpos(pos.asVec3()); sound->setPosition(objpos); if(sound->getDistanceCull()) { if((mListenerPos - objpos).length2() > 2000*2000) mOutput->finishSound(sound); } } if(!mOutput->isSoundPlaying(sound)) { mOutput->finishSound(sound); if (sound == mUnderwaterSound) mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.erase(sndidx); } else { sound->updateFade(duration); mOutput->updateSound(sound); ++sndidx; } } if(snditer->second.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.get(); if(!ptr.isEmpty() && sound->getIs3D()) { MWBase::World *world = MWBase::Environment::get().getWorld(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); sound->setPosition(pos); if(sound->getDistanceCull()) { if((mListenerPos - pos).length2() > 2000*2000) mOutput->finishStream(sound); } } if(!mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); mActiveSaySounds.erase(sayiter++); } else { sound->updateFade(duration); mOutput->updateStream(sound); ++sayiter; } } TrackList::iterator trkiter = mActiveTracks.begin(); for(;trkiter != mActiveTracks.end();++trkiter) { 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) { Sound *sound = sndbuf.first.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateSound(sound); } } for(SaySoundMap::value_type &snd : mActiveSaySounds) { Stream *sound = snd.second.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } for(SaySoundMap::value_type &snd : mSaySoundsQueue) { Stream *sound = snd.second.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); if(snditer != mActiveSounds.end()) { SoundBufferRefPairList sndlist = std::move(snditer->second); mActiveSounds.erase(snditer); mActiveSounds.emplace(updated, std::move(sndlist)); } SaySoundMap::iterator sayiter = mSaySoundsQueue.find(old); if(sayiter != mSaySoundsQueue.end()) { StreamPtr stream = std::move(sayiter->second); mSaySoundsQueue.erase(sayiter); mSaySoundsQueue.emplace(updated, std::move(stream)); } sayiter = mActiveSaySounds.find(old); if(sayiter != mActiveSaySounds.end()) { StreamPtr stream = std::move(sayiter->second); mActiveSaySounds.erase(sayiter); mActiveSaySounds.emplace(updated, std::move(stream)); } } // 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) { 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.get()); mSaySoundsQueue.clear(); for(SaySoundMap::value_type &snd : mActiveSaySounds) mOutput->finishStream(snd.second.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.47.0/apps/openmw/mwsound/soundmanagerimp.hpp000066400000000000000000000222171413061077700242720ustar00rootroot00000000000000#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 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; typedef std::map SoundMap; SoundMap mActiveSounds; 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 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(const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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) override; 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.47.0/apps/openmw/mwsound/type.hpp000066400000000000000000000006071413061077700220610ustar00rootroot00000000000000#ifndef GAME_SOUND_TYPE_H #define GAME_SOUND_TYPE_H namespace MWSound { enum class Type { Sfx = 1 << 4, /* Normal SFX sound */ Voice = 1 << 5, /* Voice sound */ Foot = 1 << 6, /* Footstep sound */ Music = 1 << 7, /* Music track */ Movie = 1 << 8, /* Movie audio track */ Mask = Sfx | Voice | Foot | Music | Movie }; } #endif openmw-openmw-0.47.0/apps/openmw/mwsound/volumesettings.cpp000066400000000000000000000026511413061077700241640ustar00rootroot00000000000000#include "volumesettings.hpp" #include #include namespace MWSound { namespace { float clamp(float value) { return std::max(0.0f, std::min(1.0f, value)); } } 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.47.0/apps/openmw/mwsound/volumesettings.hpp000066400000000000000000000007301413061077700241650ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/watersoundupdater.cpp000066400000000000000000000046041413061077700246540ustar00rootroot00000000000000#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.47.0/apps/openmw/mwsound/watersoundupdater.hpp000066400000000000000000000022011413061077700246500ustar00rootroot00000000000000#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.47.0/apps/openmw/mwstate/000077500000000000000000000000001413061077700203545ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwstate/character.cpp000066400000000000000000000114211413061077700230130ustar00rootroot00000000000000#include "character.hpp" #include #include #include #include #include bool MWState::operator< (const Slot& left, const Slot& right) { return left.mTimeStamp ignore reader.getRecHeader(); slot.mProfile.load (reader); if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!= Misc::StringUtils::lowerCase (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 for (std::string::const_iterator it = profile.mDescription.begin(); it != profile.mDescription.end(); ++it) { if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters stream << *it; 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.47.0/apps/openmw/mwstate/character.hpp000066400000000000000000000037501413061077700230260ustar00rootroot00000000000000#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); 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.47.0/apps/openmw/mwstate/charactermanager.cpp000066400000000000000000000061511413061077700243520ustar00rootroot00000000000000#include "charactermanager.hpp" #include #include #include MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::string& game) : mPath (saves), mCurrent (nullptr), mGame (game) { 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 for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) { if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters stream << *it; 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.47.0/apps/openmw/mwstate/charactermanager.hpp000066400000000000000000000026651413061077700243650ustar00rootroot00000000000000#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::string& game); 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.47.0/apps/openmw/mwstate/quicksavemanager.cpp000066400000000000000000000016471413061077700244160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwstate/quicksavemanager.hpp000066400000000000000000000020641413061077700244150ustar00rootroot00000000000000#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.47.0/apps/openmw/mwstate/statemanagerimp.cpp000066400000000000000000000575071413061077700242570ustar00rootroot00000000000000#include "statemanagerimp.hpp" #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 "../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(); } } 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::string& game) : mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), 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().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 { 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().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() +MWBase::Environment::get().getInputManager()->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); MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); MWBase::Environment::get().getWindowManager()->write(writer, listener); MWBase::Environment::get().getMechanicsManager()->write(writer, listener); MWBase::Environment::get().getInputManager()->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()); } 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); 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.intval) { 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.intval); break; case ESM::REC_DIAS: MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.intval); 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: MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap); break; case ESM::REC_CAM_: reader.getHNT(firstPersonCam, "FIRS"); break; case ESM::REC_GSCR: MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, 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.intval); break; case ESM::REC_DCOU: case ESM::REC_STLN: MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.intval); break; case ESM::REC_INPU: MWBase::Environment::get().getInputManager()->readRecord(reader, n.intval); break; default: // ignore invalid records Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toString(); 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(); } 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.47.0/apps/openmw/mwstate/statemanagerimp.hpp000066400000000000000000000061021413061077700242450ustar00rootroot00000000000000#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::string& game); 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() override; 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) override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/000077500000000000000000000000001413061077700203635ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw/mwworld/action.cpp000066400000000000000000000040011413061077700223370ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/action.hpp000066400000000000000000000021631413061077700223530ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionalchemy.cpp000066400000000000000000000013071413061077700237100ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionalchemy.hpp000066400000000000000000000004651413061077700237210ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionapply.cpp000066400000000000000000000023621413061077700234150ustar00rootroot00000000000000#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) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); actor.getClass().apply (actor, mId, actor); actor.getClass().getContainerStore(actor).remove(getTarget(), 1, 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) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1 && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } } openmw-openmw-0.47.0/apps/openmw/mwworld/actionapply.hpp000066400000000000000000000013121413061077700234140ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiondoor.cpp000066400000000000000000000005461413061077700232350ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiondoor.hpp000066400000000000000000000004761413061077700232440ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actioneat.cpp000066400000000000000000000013501413061077700230350ustar00rootroot00000000000000#include "actioneat.hpp" #include #include "../mwworld/containerstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "class.hpp" namespace MWWorld { void ActionEat::executeImp (const Ptr& actor) { // remove used item (assume the item is present in inventory) getTarget().getContainerStore()->remove(getTarget(), 1, actor); // apply to actor std::string id = getTarget().getCellRef().getRefId(); if (actor.getClass().apply (actor, id, actor) && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} } openmw-openmw-0.47.0/apps/openmw/mwworld/actioneat.hpp000066400000000000000000000004501413061077700230420ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionequip.cpp000066400000000000000000000072641413061077700234210ustar00rootroot00000000000000#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()) { std::stringstream error; error << "ActionEquip can't find item " << object.getCellRef().getRefId(); throw std::runtime_error(error.str()); } // 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.47.0/apps/openmw/mwworld/actionequip.hpp000066400000000000000000000005431413061077700234170ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionharvest.cpp000066400000000000000000000067761413061077700237610ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionharvest.hpp000066400000000000000000000006471413061077700237550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionopen.cpp000066400000000000000000000015011413061077700232230ustar00rootroot00000000000000#include "actionopen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/disease.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.47.0/apps/openmw/mwworld/actionopen.hpp000066400000000000000000000006111413061077700232310ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionread.cpp000066400000000000000000000040311413061077700231760ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionread.hpp000066400000000000000000000005511413061077700232060ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionrepair.cpp000066400000000000000000000013571413061077700235550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionrepair.hpp000066400000000000000000000005511413061077700235550ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionsoulgem.cpp000066400000000000000000000012641413061077700237430ustar00rootroot00000000000000#include "actionsoulgem.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwmechanics/actorutil.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; } MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); } } openmw-openmw-0.47.0/apps/openmw/mwworld/actionsoulgem.hpp000066400000000000000000000005351413061077700237500ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiontake.cpp000066400000000000000000000024611413061077700232140ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiontake.hpp000066400000000000000000000004541413061077700232210ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiontalk.cpp000066400000000000000000000007101413061077700232160ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiontalk.hpp000066400000000000000000000005431413061077700232270ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actionteleport.cpp000066400000000000000000000061641413061077700241320ustar00rootroot00000000000000#include "actionteleport.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.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, 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()) { int cellX; int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); world->moveObject(actor,world->getExterior(cellX,cellY), mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } else world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } } void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, 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 (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) continue; if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800) continue; out.emplace(follower); } } } openmw-openmw-0.47.0/apps/openmw/mwworld/actionteleport.hpp000066400000000000000000000023561413061077700241360ustar00rootroot00000000000000#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 includeHostiles = false); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/actiontrap.cpp000066400000000000000000000025711413061077700232400ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/actiontrap.hpp000066400000000000000000000010731413061077700232410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/cellpreloader.cpp000066400000000000000000000416311413061077700237110ustar00rootroot00000000000000#include "cellpreloader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwrender/landmanager.hpp" #include "cellstore.hpp" #include "class.hpp" 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()); bool animated = false; 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)); animated = true; } } } } if (mPreloadInstances && animated) mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); else 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) , mProgress(views.size()) , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) { } bool storeViews(double referenceTime) { for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime)) return false; return true; } void doWork() override { for (unsigned int i=0; ireset(); mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange); } } void abort() override { mAbort = true; } int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } private: std::atomic mAbort; std::vector> mProgress; int mProgressRange; std::vector > mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; }; /// 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) , mStoreViewsFailCount(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()) { // do the deletion in the background thread if (found->second.mWorkItem) { found->second.mWorkItem->abort(); mUnrefQueue->push(mPreloadCells[cell].mWorkItem); } mPreloadCells.erase(found); } } void CellPreloader::clear() { for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { if (it->second.mWorkItem) { it->second.mWorkItem->abort(); mUnrefQueue->push(it->second.mWorkItem); } 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(); mUnrefQueue->push(it->second.mWorkItem); } 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()) { if (!mTerrainPreloadItem->storeViews(timestamp)) { if (++mStoreViewsFailCount > 100) { OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; mStoreViewsFailCount = 0; } setTerrainPreloadPositions(std::vector()); } else mStoreViewsFailCount = 0; mTerrainPreloadItem = nullptr; } } 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; } void CellPreloader::setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue) { mUnrefQueue = unrefQueue; } bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) { if (!mTerrainPreloadItem) return true; else if (mTerrainPreloadItem->isDone()) { if (mTerrainPreloadItem->storeViews(timestamp)) { mTerrainPreloadItem = nullptr; return true; } else { setTerrainPreloadPositions(std::vector()); setTerrainPreloadPositions(positions); return false; } } else { progress = mTerrainPreloadItem->getProgress(); progressRange = mTerrainPreloadItem->getProgressRange(); return false; } } void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { const float resetThreshold = ESM::Land::REAL_SIZE; for (const auto& pos : mTerrainPreloadPositions) if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); mTerrainPreloadItem->waitTillDone(); } setTerrainPreloadPositions(std::vector()); } bool contains(const std::vector& container, const std::vector& contained) { for (const auto& pos : contained) { bool found = false; for (const auto& pos2 : container) { if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) { found = true; break; } } if (!found) return false; } return true; } void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (positions.empty()) mTerrainPreloadPositions.clear(); else if (contains(mTerrainPreloadPositions, positions)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { if (mTerrainViews.size() > positions.size()) { for (unsigned int i=positions.size(); ipush(mTerrainViews[i]); 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); } } } } openmw-openmw-0.47.0/apps/openmw/mwworld/cellpreloader.hpp000066400000000000000000000071521413061077700237160ustar00rootroot00000000000000#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 SceneUtil { class UnrefQueue; } namespace MWRender { class LandManager; } 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); void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; Terrain::World* mTerrain; MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; double mExpiryDelay; unsigned int mMinCacheSize; unsigned int mMaxCacheSize; bool mPreloadInstances; double mLastResourceCacheUpdate; int mStoreViewsFailCount; 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; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/cellref.cpp000066400000000000000000000140031413061077700225010ustar00rootroot00000000000000#include "cellref.hpp" #include namespace MWWorld { const ESM::RefNum& CellRef::getRefNum() const { return mCellRef.mRefNum; } bool CellRef::hasContentFile() const { return mCellRef.mRefNum.hasContentFile(); } void CellRef::unsetRefNum() { mCellRef.mRefNum.unset(); } std::string CellRef::getRefId() const { return mCellRef.mRefID; } const std::string* CellRef::getRefIdPtr() const { return &mCellRef.mRefID; } bool CellRef::getTeleport() const { return mCellRef.mTeleport; } ESM::Position CellRef::getDoorDest() const { return mCellRef.mDoorDest; } std::string CellRef::getDestCell() const { return mCellRef.mDestCell; } float CellRef::getScale() const { return mCellRef.mScale; } void CellRef::setScale(float scale) { if (scale != mCellRef.mScale) { mChanged = true; mCellRef.mScale = scale; } } ESM::Position CellRef::getPosition() const { return mCellRef.mPos; } void CellRef::setPosition(const ESM::Position &position) { mChanged = true; mCellRef.mPos = position; } float CellRef::getEnchantmentCharge() const { return mCellRef.mEnchantmentCharge; } 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; } } int CellRef::getCharge() const { return mCellRef.mChargeInt; } 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; } } float CellRef::getChargeFloat() const { return mCellRef.mChargeFloat; } void CellRef::setChargeFloat(float charge) { if (charge != mCellRef.mChargeFloat) { mChanged = true; mCellRef.mChargeFloat = charge; } } std::string CellRef::getOwner() const { return mCellRef.mOwner; } std::string CellRef::getGlobalVariable() const { return mCellRef.mGlobalVariable; } 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; } } int CellRef::getFactionRank() const { return mCellRef.mFactionRank; } void CellRef::setOwner(const std::string &owner) { if (owner != mCellRef.mOwner) { mChanged = true; mCellRef.mOwner = owner; } } std::string CellRef::getSoul() const { return mCellRef.mSoul; } void CellRef::setSoul(const std::string &soul) { if (soul != mCellRef.mSoul) { mChanged = true; mCellRef.mSoul = soul; } } std::string CellRef::getFaction() const { return mCellRef.mFaction; } void CellRef::setFaction(const std::string &faction) { if (faction != mCellRef.mFaction) { mChanged = true; mCellRef.mFaction = faction; } } int CellRef::getLockLevel() const { return mCellRef.mLockLevel; } 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 } std::string CellRef::getKey() const { return mCellRef.mKey; } std::string CellRef::getTrap() const { return mCellRef.mTrap; } void CellRef::setTrap(const std::string& trap) { if (trap != mCellRef.mTrap) { mChanged = true; mCellRef.mTrap = trap; } } int CellRef::getGoldValue() const { return mCellRef.mGoldValue; } void CellRef::setGoldValue(int value) { if (value != mCellRef.mGoldValue) { mChanged = true; mCellRef.mGoldValue = value; } } void CellRef::writeState(ESM::ObjectState &state) const { state.mRef = mCellRef; } bool CellRef::hasChanged() const { return mChanged; } } openmw-openmw-0.47.0/apps/openmw/mwworld/cellref.hpp000066400000000000000000000105631413061077700225150ustar00rootroot00000000000000#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; // Set RefNum to its default state. void unsetRefNum(); /// Does the RefNum have a content file? bool hasContentFile() const; // Id of object being referenced std::string getRefId() const; // Pointer to ID of the object being referenced const std::string* getRefIdPtr() const; // For doors - true if this door teleports to somewhere else, false // if it should open through animation. bool getTeleport() const; // Teleport location for the door, if this is a teleporting door. ESM::Position getDoorDest() const; // Destination cell for doors (optional) std::string getDestCell() const; // Scale applied to mesh float getScale() const; 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. ESM::Position getPosition() const; 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; // 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; float getChargeFloat() const; // 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) std::string getOwner() const; 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. std::string getGlobalVariable() const; void resetGlobalVariable(); // ID of creature trapped in this soul gem std::string getSoul() const; 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) std::string getFaction() const; 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; // 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; void setLockLevel(int lockLevel); void lock(int lockLevel); void unlock(); // Key and trap ID names, if any std::string getKey() const; std::string getTrap() const; void setTrap(const std::string& trap); // This is 5 for Gold_005 references, 100 for Gold_100 and so on. int getGoldValue() const; 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; private: bool mChanged; ESM::CellRef mCellRef; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/cellreflist.hpp000066400000000000000000000026201413061077700234040ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/cells.cpp000066400000000000000000000317221413061077700221760ustar00rootroot00000000000000#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().getRefIdPtr() == 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.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).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.insert (std::make_pair ( std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell, mStore, mReader))).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, std::vector& reader) : mStore (store), mReader (reader), 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.insert (std::make_pair ( std::make_pair (x, y), CellStore (cell, mStore, mReader))).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.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).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.47.0/apps/openmw/mwworld/cells.hpp000066400000000000000000000055331413061077700222040ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLS_H #define GAME_MWWORLD_CELLS_H #include #include #include #include "ptr.hpp" namespace ESM { class ESMReader; class ESMWriter; 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; std::vector& mReader; 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(); Cells (const MWWorld::ESMStore& store, std::vector& 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.47.0/apps/openmw/mwworld/cellstore.cpp000066400000000000000000001222331413061077700230660ustar00rootroot00000000000000#include "cellstore.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/recharge.hpp" #include "ptr.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.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.getRefIdPtr() == 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.pos[0], newpos.pos[1], newpos.pos[2]); if (!iter->mData.isEnabled()) { iter->mData.enable(); MWBase::Environment::get().getWorld()->disable(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); } } 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"); // Objects with no refnum can't be handled correctly in the merging process that happens // on a save/load, so do a simple copy & delete for these objects. if (!object.getCellRef().getRefNum().hasContentFile()) { MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount()); object.getRefData().setCount(0); object.getRefData().setBaseNode(nullptr); return copied; } 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, std::vector& readerList) : mStore(esmStore), mReader(readerList), 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().getRefIdPtr() == *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() { std::vector& esm = mReader; 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. int index = mCell->mContextList[i].index; mCell->restore (esm[index], i); ESM::CellRef ref; // Get each reference in turn bool deleted = false; while (mCell->getNextRef (esm[index], ref, deleted)) { if (deleted) 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() { std::vector& esm = mReader; 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. int index = mCell->mContextList[i].index; mCell->restore (esm[index], i); ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn bool deleted = false; while(mCell->getNextRef(esm[index], ref, deleted)) { // 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: { if (ref.mRefNum.fromGroundcoverFile()) return; 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.reset(new ESM::FogState()); 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)"; reader.skipHSubUntil("OBJE"); 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(ESM::FogState *fog) { mFogState.reset(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(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)); } } openmw-openmw-0.47.0/apps/openmw/mwworld/cellstore.hpp000066400000000000000000000472601413061077700231010ustar00rootroot00000000000000#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 "timestamp.hpp" #include "ptr.hpp" namespace ESM { struct Cell; struct CellState; struct FogState; 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; std::vector& mReader; // 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::shared_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(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, std::vector& readerList); 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 (ESM::FogState* 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. 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.47.0/apps/openmw/mwworld/cellvisitors.hpp000066400000000000000000000010071413061077700236140ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLVISITORS_H #define GAME_MWWORLD_CELLVISITORS_H #include #include #include "ptr.hpp" namespace MWWorld { struct ListAndResetObjectsVisitor { std::vector mObjects; bool operator() (MWWorld::Ptr ptr) { if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); mObjects.push_back (ptr); } return true; } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/class.cpp000066400000000000000000000360771413061077700222110ustar00rootroot00000000000000#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 "refdata.hpp" #include "nullaction.hpp" #include "failedaction.hpp" #include "actiontake.hpp" #include "containerstore.hpp" #include "../mwgui/tooltips.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWWorld { std::map > Class::sClasses; Class::Class() {} Class::~Class() {} void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, 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::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return std::shared_ptr (new NullAction); } std::shared_ptr Class::use (const Ptr& ptr, bool force) const { return std::shared_ptr (new NullAction); } 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 (const std::string& key) { if (key.empty()) throw std::logic_error ("Class::get(): attempting to get an empty key"); std::map >::const_iterator iter = sClasses.find (key); if (iter==sClasses.end()) throw std::logic_error ("Class::get(): unknown class key: " + key); return *iter->second; } bool Class::isPersistent(const ConstPtr &ptr) const { throw std::runtime_error ("class does not support persistence"); } void Class::registerClass(const std::string& key, std::shared_ptr instance) { instance->mTypeName = key; sClasses.insert(std::make_pair(key, 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::shared_ptr Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::shared_ptr(new NullAction()); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfItem"); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } std::shared_ptr action(new ActionTake(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::CreatureStats::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.47.0/apps/openmw/mwworld/class.hpp000066400000000000000000000417771413061077700222210ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CLASS_H #define GAME_MWWORLD_CLASS_H #include #include #include #include #include #include "ptr.hpp" #include "doorstate.hpp" #include "../mwmechanics/creaturestats.hpp" namespace ESM { struct ObjectState; } namespace MWRender { class RenderingInterface; } namespace MWPhysics { class PhysicsSystem; } namespace MWMechanics { class NpcStats; struct Movement; } 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 { static std::map > sClasses; std::string mTypeName; // not implemented Class (const Class&); Class& operator= (const Class&); protected: Class(); std::shared_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(); const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). 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::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). virtual std::shared_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 apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const; ///< Apply \a id on \a ptr. /// \param actor Actor that is resposible for the ID being applied to \a ptr. /// \return Any effect? /// /// (default implementation: ignore and return false) 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 (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. static void registerClass (const std::string& key, std::shared_ptr 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::CreatureStats::AiSetting setting, int value) const; virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/containerstore.cpp000066400000000000000000001300411413061077700241250ustar00rootroot00000000000000#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" 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 auto& iter : cellRefList.mList) { if (iter.mData.getCount()>0) sum += iter.mData.getCount()*iter.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 (auto& iter : list.mList) { if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount()) { MWWorld::Ptr ptr (&iter, 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 auto& iter : collection.mList) { if (iter.mData.getCount() == 0) continue; ESM::ObjectState state; storeState (iter, state); if (equipable) storeEquipmentState(iter, index, inventory); inventory.mItems.push_back (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(const std::string &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(const std::string &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(const std::string& 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::Seed& seed) { for (const ESM::ContItem& iter : items.mList) { std::string id = Misc::StringUtils::lowerCase(iter.mItem); addInitialItem(id, owner, iter.mCount, &seed); } 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::Seed* seed, 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, seed, 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, seed, 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::Seed* seed, bool topLevel) { if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) { if(!seed) 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, seed, true); return; } else { std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *seed); if (itemId.empty()) return; addInitialItem(itemId, owner, count, seed, 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::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); 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::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); 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.getTypeName()==typeid (ESM::Potion).name()) return Type_Potion; if (ptr.getTypeName()==typeid (ESM::Apparatus).name()) return Type_Apparatus; if (ptr.getTypeName()==typeid (ESM::Armor).name()) return Type_Armor; if (ptr.getTypeName()==typeid (ESM::Book).name()) return Type_Book; if (ptr.getTypeName()==typeid (ESM::Clothing).name()) return Type_Clothing; if (ptr.getTypeName()==typeid (ESM::Ingredient).name()) return Type_Ingredient; if (ptr.getTypeName()==typeid (ESM::Light).name()) return Type_Light; if (ptr.getTypeName()==typeid (ESM::Lockpick).name()) return Type_Lockpick; if (ptr.getTypeName()==typeid (ESM::Miscellaneous).name()) return Type_Miscellaneous; if (ptr.getTypeName()==typeid (ESM::Probe).name()) return Type_Probe; if (ptr.getTypeName()==typeid (ESM::Repair).name()) return Type_Repair; if (ptr.getTypeName()==typeid (ESM::Weapon).name()) return Type_Weapon; throw std::runtime_error ( "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " 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.47.0/apps/openmw/mwworld/containerstore.hpp000066400000000000000000000371541413061077700241450ustar00rootroot00000000000000#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() {} }; 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::Seed* seed, bool topLevel=true); void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, 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(const std::string& id, int count, const Ptr& actorPtr); ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) int remove(const std::string& 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 (const std::string& 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::Seed& seed = Misc::Rng::getSeed()); ///< 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 : public std::iterator { 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: 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.47.0/apps/openmw/mwworld/contentloader.hpp000066400000000000000000000013641413061077700237410ustar00rootroot00000000000000#ifndef CONTENTLOADER_HPP #define CONTENTLOADER_HPP #include #include #include #include "components/loadinglistener/loadinglistener.hpp" namespace MWWorld { struct ContentLoader { ContentLoader(Loading::Listener& listener) : mListener(listener) { } virtual ~ContentLoader() { } virtual void load(const boost::filesystem::path& filepath, int& index) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); } protected: Loading::Listener& mListener; }; } /* namespace MWWorld */ #endif /* CONTENTLOADER_HPP */ openmw-openmw-0.47.0/apps/openmw/mwworld/customdata.cpp000066400000000000000000000044261413061077700232410ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/customdata.hpp000066400000000000000000000031321413061077700232370ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/datetimemanager.cpp000066400000000000000000000126631413061077700242260ustar00rootroot00000000000000#include "datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.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; int days = static_cast(hour / 24); hour = std::fmod(hour, 24); mGameHour = static_cast(hour); if (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(const std::string& 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(const std::string& 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.47.0/apps/openmw/mwworld/datetimemanager.hpp000066400000000000000000000017061413061077700242270ustar00rootroot00000000000000#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(const std::string& name, int value); bool updateGlobalFloat(const std::string& name, float value); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/doorstate.hpp000066400000000000000000000003031413061077700230740ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/esmloader.cpp000066400000000000000000000013071413061077700230430ustar00rootroot00000000000000#include "esmloader.hpp" #include "esmstore.hpp" #include namespace MWWorld { EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) : ContentLoader(listener) , mEsm(readers) , mStore(store) , mEncoder(encoder) { } void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { ContentLoader::load(filepath.filename(), index); ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); lEsm.setGlobalReaderList(&mEsm); lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ openmw-openmw-0.47.0/apps/openmw/mwworld/esmloader.hpp000066400000000000000000000012231413061077700230450ustar00rootroot00000000000000#ifndef ESMLOADER_HPP #define ESMLOADER_HPP #include #include "contentloader.hpp" namespace ToUTF8 { class Utf8Encoder; } namespace ESM { class ESMReader; } namespace MWWorld { class ESMStore; struct EsmLoader : public ContentLoader { EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); void load(const boost::filesystem::path& filepath, int& index) override; private: std::vector& mEsm; MWWorld::ESMStore& mStore; ToUTF8::Utf8Encoder* mEncoder; }; } /* namespace MWWorld */ #endif // ESMLOADER_HPP openmw-openmw-0.47.0/apps/openmw/mwworld/esmstore.cpp000066400000000000000000000403401413061077700227310ustar00rootroot00000000000000#include "esmstore.hpp" #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, std::vector& readers) { for (size_t i = 0; i < cell.mContextList.size(); i++) { size_t index = cell.mContextList[i].index; if (readers.size() <= index) readers.resize(index + 1); cell.restore(readers[index], i); ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; bool deleted = false; while(cell.getNextRef(readers[index], 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); } } } std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::map& npcs) { // Cache first class from store - we will use it if current class is not found std::string defaultCls; auto it = classes.begin(); if (it != classes.end()) defaultCls = it->mId; else throw std::runtime_error("List of NPC classes is empty!"); // 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; } } std::string npcClass = npc.mClass; if (!npcClass.empty()) { 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; } } 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) { listener->setProgressRange(1000); ESM::Dialogue *dialogue = nullptr; // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here to avoid continual resizes during loading, // and so we can properly verify if valid plugin indices are being passed to the // LandTexture Store retrieval methods. mLandTextures.resize(esm.getGlobalReaderList()->size()); /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate // refnumber mangling, as required for handling moved references. const std::vector &masters = esm.getGameFiles(); std::vector *allPlugins = esm.getGlobalReaderList(); for (size_t j = 0; j < masters.size(); j++) { const ESM::Header::MasterData &mast = masters[j]; std::string fname = mast.name; int index = ~0; for (int i = 0; i < esm.getIndex(); i++) { const std::string candidate = allPlugins->at(i).getContext().filename; std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; break; } } if (index == (int)~0) { // Tried to load a parent file that has not been loaded yet. This is bad, // the launcher should have taken care of this. std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name + ", but it has not been loaded yet. Please check your load order."; esm.fail(fstring); } esm.addParentFileIndex(index); } // Loop through all records while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); esm.getRecHeader(); // Look up the record type. std::map::iterator it = mStores.find(n.intval); if (it == mStores.end()) { if (n.intval == 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.intval == ESM::REC_MGEF) { mMagicEffects.load (esm); } else if (n.intval == ESM::REC_SKIL) { mSkills.load (esm); } else if (n.intval==ESM::REC_FILT || n.intval == ESM::REC_DBGP) { // ignore project file only records esm.skipRecord(); } else { std::stringstream error; error << "Unknown record: " << n.toString(); throw std::runtime_error(error.str()); } } else { RecordId id = it->second->load(esm); if (id.mIsDeleted) { it->second->eraseStatic(id.mId); continue; } if (n.intval==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = nullptr; } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); } } void ESMStore::setUp(bool validateRecords) { 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()) mStaticIds = mIds; mSkills.setUp(); mMagicEffects.setUp(); mAttributes.setUp(); mDialogs.setUp(); if (validateRecords) { validate(); countRecords(); } } void ESMStore::countRecords() { if(!mRefCount.empty()) return; std::vector refs; std::vector refIDs; std::vector readers; 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]; 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); } 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& originalId) const { const std::string id = Misc::StringUtils::lowerCase(originalId); 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.47.0/apps/openmw/mwworld/esmstore.hpp000066400000000000000000000366301413061077700227450ustar00rootroot00000000000000#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 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. std::map mIds; std::map mStaticIds; std::unordered_map mRefCount; std::map mStores; unsigned int mDynamicCount; mutable std::map > mSpellListCache; /// Validate entries in store after setup void validate(); void countRecords(); public: /// \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. /// \note id must be in lower case. int find(const std::string &id) const { std::map::const_iterator it = mIds.find(id); if (it == mIds.end()) { return 0; } return it->second; } int findStatic(const std::string &id) const { std::map::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); 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(bool validateRecords = false); 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.47.0/apps/openmw/mwworld/failedaction.cpp000066400000000000000000000007771413061077700235240ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/failedaction.hpp000066400000000000000000000006021413061077700235140ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/globals.cpp000066400000000000000000000060371413061077700225200ustar00rootroot00000000000000#include "globals.hpp" #include #include #include #include #include "esmstore.hpp" namespace MWWorld { Globals::Collection::const_iterator Globals::find (const std::string& name) const { Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); return iter; } Globals::Collection::iterator Globals::find (const std::string& name) { Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + 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[] (const std::string& name) const { return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (const std::string& name) { return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (const std::string& 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.47.0/apps/openmw/mwworld/globals.hpp000066400000000000000000000026121413061077700225200ustar00rootroot00000000000000#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 (const std::string& name) const; Collection::iterator find (const std::string& name); public: const ESM::Variant& operator[] (const std::string& name) const; ESM::Variant& operator[] (const std::string& name); char getType (const std::string& 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.47.0/apps/openmw/mwworld/inventorystore.cpp000066400000000000000000001057241413061077700242120ustar00rootroot00000000000000#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) , mMagicEffects(store.mMagicEffects) , mInventoryListener(store.mInventoryListener) , mUpdatesEnabled(store.mUpdatesEnabled) , mFirstAutoEquip(store.mFirstAutoEquip) , mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes) , mSelectedEnchantItem(end()) { copySlots (store); } MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { if (this == &store) return *this; mListener = store.mListener; mInventoryListener = store.mInventoryListener; mMagicEffects = store.mMagicEffects; mFirstAutoEquip = store.mFirstAutoEquip; mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; 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()) { std::string type = itemPtr.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) 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); updateMagicEffects(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); updateMagicEffects(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.getTypeName() == typeid(ESM::Armor).name()) { 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.getTypeName() == typeid(ESM::Clothing).name()) { // 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().getEnchantment (**iter); if (!enchantmentId.empty()) { const ESM::Enchantment& enchantment = *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; std::vector params; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { params.resize(enchantment.mEffects.mList.size()); int i=0; for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { int delta = effect.mMagnMax - effect.mMagnMin; // Roll some dice, one for each effect if (delta) params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast(delta); // Try resisting each effect params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor); ++i; } // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params; } else params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; int i=0; for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effect.mEffectID); // Fully resisted or can't be applied to target? if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer())) { i++; continue; } float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom; magnitude *= params[i].mMultiplier; if (!existed) { // During first auto equip, we don't play any sounds. // Basically we don't want sounds when the actor is first loaded, // the items should appear as if they'd always been equipped. mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip); } if (magnitude) mMagicEffects.add (effect, magnitude); i++; } } } // Now drop expired effects for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end();) { bool found = false; for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter == end()) continue; if ((**iter).getCellRef().getRefId() == it->first) { found = true; } } if (!found) mPermanentMagicEffectMagnitudes.erase(it++); else ++it; } // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); mFirstAutoEquip = false; } bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); if (!canStack) return false; // don't stack if either item is currently equipped for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter != end() && (ptr1 == **iter || ptr2 == **iter)) { bool stackWhenEquipped = (*iter)->getClass().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()) { std::string type = item.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) 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); updateMagicEffects(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() { return mInventoryListener; } void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor) { mInventoryListener = listener; updateMagicEffects(actor); } 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::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) { for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter==end()) continue; std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); if (enchantmentId.empty()) continue; const ESM::Enchantment& enchantment = *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end()) continue; int i=0; for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { i++; // Don't get spell icon display information for enchantments that weren't actually applied if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0) continue; const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1]; float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; if (magnitude > 0) visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); } } } void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) { for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) { if (*it != end()) purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); } } void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex) { TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) return; for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter==end()) continue; if ((*iter)->getCellRef().getRefId() != sourceId) continue; std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); if (!enchantmentId.empty()) { const ESM::Enchantment& enchantment = *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; std::vector& params = effectMagnitudeIt->second; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) { if (effectIt->mEffectID != effectId) continue; if (effectIndex >= 0 && effectIndex != i) continue; if (wholeSpell) { mPermanentMagicEffectMagnitudes.erase(sourceId); return; } float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, -magnitude); params[i].mMultiplier = 0; } } } } 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; } void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const { MWWorld::ContainerStore::writeState(state); for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) { std::vector > params; for (std::vector::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) { params.emplace_back(pIt->mRandom, pIt->mMultiplier); } state.mPermanentMagicEffectMagnitudes[it->first] = params; } } void MWWorld::InventoryStore::readState(const ESM::InventoryState &state) { MWWorld::ContainerStore::readState(state); for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin(); it != state.mPermanentMagicEffectMagnitudes.end(); ++it) { std::vector params; for (std::vector >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) { EffectParams p; p.mRandom = pIt->first; p.mMultiplier = pIt->second; params.push_back(p); } mPermanentMagicEffectMagnitudes[it->first] = params; } } openmw-openmw-0.47.0/apps/openmw/mwworld/inventorystore.hpp000066400000000000000000000221331413061077700242070ustar00rootroot00000000000000#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 () {} /** * @param effect * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) * or was it loaded from a savegame / initial game state? \n * If it isn't new, non-looping VFX should not be played. * @param playSound Play effect sound? */ virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {} 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: MWMechanics::MagicEffects mMagicEffects; 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; // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. struct EffectParams { // Modifier to scale between min and max magnitude float mRandom; // Multiplier for when an effect was fully or partially resisted float mMultiplier; }; typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; 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 updateMagicEffects(const Ptr& actor); 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. const MWMechanics::MagicEffects& getMagicEffects() const; ///< Return magic effects from worn items. 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(); void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); void purgeEffect (short effectId, bool wholeSpell = false); ///< Remove a magic effect void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1); ///< Remove a magic effect void clear() override; ///< Empty container. void writeState (ESM::InventoryState& state) const override; void readState (const ESM::InventoryState& state) override; }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/livecellref.cpp000066400000000000000000000041431413061077700233650ustar00rootroot00000000000000#include "livecellref.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "ptr.hpp" #include "class.hpp" #include "esmstore.hpp" MWWorld::LiveCellRefBase::LiveCellRefBase(const std::string& 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()); } } void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const { mRef.writeState(state); ConstPtr ptr (this); mData.write (state, mClass->getScript (ptr)); mClass->writeAdditionalState (ptr, state); } bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) { return true; } openmw-openmw-0.47.0/apps/openmw/mwworld/livecellref.hpp000066400000000000000000000072361413061077700234000ustar00rootroot00000000000000#ifndef GAME_MWWORLD_LIVECELLREF_H #define GAME_MWWORLD_LIVECELLREF_H #include #include "cellref.hpp" #include "refdata.hpp" namespace ESM { struct ObjectState; } namespace MWWorld { class Ptr; class ESMStore; class Class; /// 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(const std::string& 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. 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; } /// 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(typeid(X).name(), cref), mBase(b) {} LiveCellRef(const X* b = nullptr) : LiveCellRefBase(typeid(X).name()), 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. 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.47.0/apps/openmw/mwworld/localscripts.cpp000066400000000000000000000114131413061077700235710ustar00rootroot00000000000000#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.getTypeName() == typeid(ESM::Container).name() && 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.47.0/apps/openmw/mwworld/localscripts.hpp000066400000000000000000000026211413061077700235770ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/manualref.cpp000066400000000000000000000062301413061077700230420ustar00rootroot00000000000000#include "manualref.hpp" #include "esmstore.hpp" namespace { template void create(const MWWorld::Store& list, const std::string& name, boost::any& refValue, MWWorld::Ptr& ptrValue) { const T* base = list.find(name); ESM::CellRef cellRef; cellRef.mRefNum.unset(); cellRef.mRefID = name; cellRef.mScale = 1; cellRef.mFactionRank = 0; cellRef.mChargeInt = -1; cellRef.mChargeIntRemainder = 0.0f; cellRef.mGoldValue = 1; cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; MWWorld::LiveCellRef ref(cellRef, base); refValue = ref; ptrValue = MWWorld::Ptr(&boost::any_cast&>(refValue), nullptr); } } MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const std::string& 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.47.0/apps/openmw/mwworld/manualref.hpp000066400000000000000000000011131413061077700230420ustar00rootroot00000000000000#ifndef GAME_MWWORLD_MANUALREF_H #define GAME_MWWORLD_MANUALREF_H #include #include "ptr.hpp" namespace MWWorld { /// \brief Manually constructed live cell ref class ManualRef { boost::any mRef; Ptr mPtr; ManualRef (const ManualRef&); ManualRef& operator= (const ManualRef&); public: ManualRef(const MWWorld::ESMStore& store, const std::string& name, const int count = 1); const Ptr& getPtr() const { return mPtr; } }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/nullaction.hpp000066400000000000000000000005041413061077700232430ustar00rootroot00000000000000#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.47.0/apps/openmw/mwworld/player.cpp000066400000000000000000000364021413061077700223700ustar00rootroot00000000000000#include "player.hpp" #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.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 "class.hpp" #include "ptr.hpp" #include "cellstore.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), mAttackingOrSpell(false), 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(int(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(int(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.setBase(int(gmst.find(name)->mValue.getFloat())); 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.setBase(int(gmst.find(name)->mValue.getFloat())); npcStats.setSkill(i, value); } } 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) { mAttackingOrSpell = attackingOrSpell; } bool Player::getAttackingOrSpell() const { return mAttackingOrSpell; } 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; mAttackingOrSpell = 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; i().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(); } } openmw-openmw-0.47.0/apps/openmw/mwworld/player.hpp000066400000000000000000000103701413061077700223710ustar00rootroot00000000000000#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 namespace ESM { struct NPC; 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 MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; bool mAttackingOrSpell; 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); bool getAttackingOrSpell() const; 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); }; } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/projectilemanager.cpp000066400000000000000000000722741413061077700245760ustar00rootroot00000000000000#include "projectilemanager.hpp" #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 "../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 osg::NodeCallback { public: RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) : mAxis(axis) , mRotateSpeed(rotateSpeed) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osg::PositionAttitudeTransform* transform = static_cast(node); double time = nv->getFrameStamp()->getSimulationTime(); osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); transform->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) 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("meshes\\" + weapon->mModel, 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); } SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; state.mNode->accept(disableFreezeOnCullVisitor); state.mNode->addCullCallback(new SceneUtil::LightListCallback); mParent->addChild(state.mNode); state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); 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) { 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; 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 = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic.at(1))->mModel; state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } void ProjectileManager::launchProjectile(Ptr actor, ConstPtr projectile, const osg::Vec3f &pos, const osg::Quat &orient, 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) { 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; } } osg::Quat orient = magicBoltState.mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->mValue.getFloat(); float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); osg::Vec3f newPos = projectile->getPosition() + direction * duration * 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); mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } 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; osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; // 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); mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } void ProjectileManager::processHits() { for (auto& projectileState : mProjectiles) { if (projectileState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); const auto pos = projectile->getPosition(); 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); cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); const auto pos = projectile->getPosition(); 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.mStack = false; cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); 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.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; 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.47.0/apps/openmw/mwworld/projectilemanager.hpp000066400000000000000000000077001413061077700245730ustar00rootroot00000000000000#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); void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& pos, const osg::Quat& orient, 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; 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.47.0/apps/openmw/mwworld/ptr.cpp000066400000000000000000000033741413061077700217030ustar00rootroot00000000000000#include "ptr.hpp" #include #include "containerstore.hpp" #include "class.hpp" #include "livecellref.hpp" const std::string& MWWorld::Ptr::getTypeName() const { if(mRef != nullptr) return mRef->mClass->getTypeName(); throw std::runtime_error("Can't get type name from an empty object."); } MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const { if (!mRef) throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); return mRef; } MWWorld::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); return mRef->mRef; } MWWorld::RefData& MWWorld::Ptr::getRefData() const { assert(mRef); return mRef->mData; } void MWWorld::Ptr::setContainerStore (ContainerStore *store) { assert (store); assert (!mCell); mContainerStore = store; } MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const { return mContainerStore; } MWWorld::Ptr::operator const void *() { return mRef; } // ------------------------------------------------------------------------------- const std::string &MWWorld::ConstPtr::getTypeName() const { if(mRef != nullptr) return mRef->mClass->getTypeName(); throw std::runtime_error("Can't get type name from an empty object."); } const MWWorld::LiveCellRefBase *MWWorld::ConstPtr::getBase() const { if (!mRef) throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); return mRef; } void MWWorld::ConstPtr::setContainerStore (const ContainerStore *store) { assert (store); assert (!mCell); mContainerStore = store; } const MWWorld::ContainerStore *MWWorld::ConstPtr::getContainerStore() const { return mContainerStore; } MWWorld::ConstPtr::operator const void *() { return mRef; } openmw-openmw-0.47.0/apps/openmw/mwworld/ptr.hpp000066400000000000000000000140531413061077700217040ustar00rootroot00000000000000#ifndef GAME_MWWORLD_PTR_H #define GAME_MWWORLD_PTR_H #include #include #include #include "livecellref.hpp" namespace MWWorld { class ContainerStore; class CellStore; struct LiveCellRefBase; /// \brief Pointer to a LiveCellRef class Ptr { public: MWWorld::LiveCellRefBase *mRef; CellStore *mCell; ContainerStore *mContainerStore; public: Ptr(MWWorld::LiveCellRefBase *liveCellRef=nullptr, CellStore *cell=nullptr) : mRef(liveCellRef), mCell(cell), mContainerStore(nullptr) { } bool isEmpty() const { return mRef == nullptr; } const std::string& getTypeName() const; const Class& getClass() const { if(mRef != nullptr) return *(mRef->mClass); throw std::runtime_error("Cannot get class of an empty object"); } template MWWorld::LiveCellRef *get() const { MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); if(ref) return ref; std::stringstream str; str<< "Bad LiveCellRef cast to "<mClass); throw std::runtime_error("Cannot get class of an empty object"); } template const MWWorld::LiveCellRef *get() const { const MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); if(ref) return ref; std::stringstream str; str<< "Bad LiveCellRef cast to "<mRef; } const RefData& getRefData() const { assert(mRef); return mRef->mData; } const CellStore *getCell() const { assert(mCell); return mCell; } bool isInCell() const { return (mContainerStore == nullptr) && (mCell != nullptr); } void setContainerStore (const ContainerStore *store); ///< Must not be called on references that are in a cell. const ContainerStore *getContainerStore() const; ///< May return a 0-pointer, if reference is not in a container. operator const void *(); ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty }; inline bool operator== (const Ptr& left, const Ptr& right) { return left.mRef==right.mRef; } inline bool operator!= (const Ptr& left, const Ptr& right) { return !(left==right); } inline bool operator< (const Ptr& left, const Ptr& right) { return left.mRef= (const Ptr& left, const Ptr& right) { return !(left (const Ptr& left, const Ptr& right) { return rightright); } inline bool operator== (const ConstPtr& left, const ConstPtr& right) { return left.mRef==right.mRef; } inline bool operator!= (const ConstPtr& left, const ConstPtr& right) { return !(left==right); } inline bool operator< (const ConstPtr& left, const ConstPtr& right) { return left.mRef= (const ConstPtr& left, const ConstPtr& right) { return !(left (const ConstPtr& left, const ConstPtr& right) { return rightright); } } #endif openmw-openmw-0.47.0/apps/openmw/mwworld/recordcmp.hpp000066400000000000000000000016301413061077700230520ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_RECORDCMP_H #define OPENMW_MWWORLD_RECORDCMP_H #include #include namespace MWWorld { struct RecordCmp { template bool operator()(const T &x, const T& y) const { return x.mId < y.mId; } }; template <> inline bool RecordCmp::operator()(const ESM::Dialogue &x, const ESM::Dialogue &y) const { return Misc::StringUtils::ciLess(x.mId, y.mId); } template <> inline bool RecordCmp::operator()(const ESM::Cell &x, const ESM::Cell &y) const { return Misc::StringUtils::ciLess(x.mName, y.mName); } template <> inline bool RecordCmp::operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { return Misc::StringUtils::ciLess(x.mCell, y.mCell); } } // end namespace #endif openmw-openmw-0.47.0/apps/openmw/mwworld/refdata.cpp000066400000000000000000000151571413061077700225060ustar00rootroot00000000000000#include "refdata.hpp" #include #include "customdata.hpp" #include "cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.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::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; mAnimationState = refData.mAnimationState; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : nullptr; } void RefData::cleanup() { mBaseNode = nullptr; mCustomData = nullptr; } RefData::RefData() : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), 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), 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), 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 (...) {} } 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.47.0/apps/openmw/mwworld/refdata.hpp000066400000000000000000000106741413061077700225120ustar00rootroot00000000000000#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 MWWorld { class CustomData; class RefData { SceneUtil::PositionAttitudeTransform* mBaseNode; MWScript::Locals mLocals; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. bool mDeletedByContentFile; bool mEnabled; /// 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 = default; ~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 = default; /// 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); 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.47.0/apps/openmw/mwworld/scene.cpp000066400000000000000000001340071413061077700221710ustar00rootroot00000000000000#include "scene.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/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.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" 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 makeObjectOsgQuat(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(zr, osg::Vec3(0, 0, -1)) * osg::Quat(yr, osg::Vec3(0, -1, 0)) * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) { if (!ptr.getRefData().getBaseNode()) return; rendering.rotateObject(ptr, ptr.getClass().isActor() ? makeActorOsgQuat(ptr.getRefData().getPosition()) : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) : makeObjectOsgQuat(ptr.getRefData().getPosition())) ); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) { bool useAnim = ptr.getClass().useAnim(); std::string model = ptr.getClass().getModel(ptr); if (useAnim) model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); const std::string &id = ptr.getCellRef().getRefId(); if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player return model; } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { Log(Debug::Warning) << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice"; return; } bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) 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, RotationOrder::direct); ptr.getClass().insertObject (ptr, model, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); if (ptr.getClass().isActor()) rendering.addWaterRippleEmitter(ptr); // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) { if (const auto object = physics.getObject(ptr)) { if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { btVector3 aabbMin; btVector3 aabbMax; object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto center = (aabbMax + aabbMin) * 0.5f; const auto distanceFromDoor = MWBase::Environment::get().getWorld()->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::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), transform.getOrigin() ); const auto start = Misc::Convert::makeOsgVec3f(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::makeOsgVec3f(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(), connectionStart, connectionEnd), transform ); } else { navigator.addObject( DetourNavigator::ObjectId(object), DetourNavigator::ObjectShapes(object->getShapeInstance()), object->getTransform() ); } } else if (physics.getActor(ptr)) { navigator.addAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); } } struct InsertVisitor { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; bool mTest; std::vector mToInsert; InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); template void insert(AddObject&& addObject); }; InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} 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 (!mTest) mLoadingListener.increaseProgress (1); } } struct PositionVisitor { bool operator() (const MWWorld::Ptr& ptr) { if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) ptr.getClass().adjustPosition (ptr, false); return true; } }; int getCellPositionDistanceToOrigin(const std::pair& cellPosition) { return std::abs(cellPosition.first) + std::abs(cellPosition.second); } } namespace MWWorld { void Scene::removeFromPagedRefs(const Ptr &ptr) { const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (refnum.hasContentFile() && mPagedRefs.erase(refnum)) { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); setNodeRotation(ptr, mRendering, 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) { setNodeRotation(ptr, mRendering, order); mPhysics->updateRotation(ptr); } 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, bool paused) { mPreloader->updateCache(mRendering.getReferenceTime()); preloadCells(duration); mRendering.update (duration, paused); } void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { if (!test) Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) navigator->removeObject(DetourNavigator::ObjectId(object)); else if (mPhysics->getActor(ptr)) { navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } mPhysics->remove(ptr); } const auto cellX = (*iter)->getCell()->getGridX(); const auto cellY = (*iter)->getCell()->getGridY(); if ((*iter)->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); mPhysics->removeHeightField(cellX, cellY); } if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (*iter); mRendering.removeCell(*iter); MWBase::Environment::get().getWindowManager()->removeCell(*iter); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { std::pair result = mActiveCells.insert(cell); if(result.second) { if (test) Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); else Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; const auto world = MWBase::Environment::get().getWorld(); const auto navigator = world->getNavigator(); const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); // Load terrain physics first... if (!test && 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; if (data) { mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { static std::vector defaultHeight; defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->addObject(DetourNavigator::ObjectId(heightField), heightField, *heightField->getShape(), heightField->getCollisionObject()->getWorldTransform()); } if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) navigator->addPathgrid(*cell->getCell(), *pathgrid); // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); if (respawn) cell->respawn(); // ... then references. This is important for adjustPosition to work correctly. insertCell (*cell, loadingListener, test); mRendering.addCell(cell); if (!test) { 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)) navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); } else { navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), cell->getWaterLevel(), btTransform::getIdentity()); } } else mPhysics->disableWater(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) { mRendering.configureAmbient(cell->getCell()); } } } mPreloader->notifyLoaded(cell); } void Scene::clear() { CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) unloadCell (active++); 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; MWBase::Environment::get().getWorld()->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; } osg::Vec2i newCenter; MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y()); return newCenter; } void Scene::playerMoved(const osg::Vec3f &pos) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->updatePlayerPosition(player.getRefData().getPosition().asVec3()); if (!mCurrentCell || !mCurrentCell->isExterior()) return; osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); if (newCell != mCurrentGridCenter) changeCellGrid(pos, newCell.x(), newCell.y()); } void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) { if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) { // keep cells within the new grid ++active; continue; } } unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); preloadTerrain(pos, true); mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; std::vector> cellsPositionsToLoad; // get the number of refs to load for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter!=mActiveCells.end()) { assert ((*iter)->getCell()->isExterior()); if (x==(*iter)->getCell()->getGridX() && y==(*iter)->getCell()->getGridY()) break; ++iter; } if (iter==mActiveCells.end()) { refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); cellsPositionsToLoad.emplace_back(x, y); } } } 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); }); // Load cells for (const auto& cellPosition : cellsPositionsToLoad) { const auto x = cellPosition.first; const auto y = cellPosition.second; CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) { assert ((*iter)->getCell()->isExterior()); if (x == (*iter)->getCell()->getGridX() && y == (*iter)->getCell()->getGridY()) break; ++iter; } if (iter == mActiveCells.end()) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); loadCell (cell, loadingListener, changeEvent); } } CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY); MWBase::Environment::get().getWindowManager()->changeCell(current); if (changeEvent) mCellChanged = true; mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); } 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 = MWBase::Environment::get().getWorld()->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())+")..."); CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); loadCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { unloadCell(iter, true); break; } ++iter; } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); 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 = MWBase::Environment::get().getWorld()->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 = MWBase::Environment::get().getWorld()->getInterior(it->mName); loadCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); if (it->mName == (*iter)->getCell()->mName) { unloadCell(iter, true); break; } ++iter; } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); 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()); MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr old = world->getPlayerPtr(); world->getPlayer().setCell(cell); MWWorld::Ptr player = world->getPlayerPtr(); mRendering.updatePlayerPtr(player); if (adjustPlayerPos) { world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]); float x = pos.rot[0]; float y = pos.rot[1]; float z = pos.rot[2]; world->rotateObject(player, x, y, z); player.getClass().adjustPosition(player, true); } MWBase::Environment::get().getMechanicsManager()->updateCell(old, player); MWBase::Environment::get().getWindowManager()->watchActor(player); mPhysics->updatePtr(old, player); world->adjustSky(); mLastPlayerPos = player.getRefData().getPosition().asVec3(); } Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) : mCurrentCell (nullptr), mCellChanged (false), 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.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); mPreloader->setUnrefQueue(rendering.getUnrefQueue()); mPhysics->setUnrefQueue(rendering.getUnrefQueue()); 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() { } 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 = MWBase::Environment::get().getWorld()->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) { MWBase::World *world = MWBase::Environment::get().getWorld(); world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]); float x = position.rot[0]; float y = position.rot[1]; float z = position.rot[2]; world->rotateObject(world->getPlayerPtr(), x, y, z); if (adjustPlayerPos) world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } Log(Debug::Info) << "Changing to interior"; // unload CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) unloadCell (active++); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); // adjust fog mRendering.configureFog(mCurrentCell->getCell()); // Sky system MWBase::Environment::get().getWorld()->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); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { int x = 0; int y = 0; MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); } CellStore* Scene::getCurrentCell () { return mCurrentCell; } void Scene::markCellAsUnchanged() { mCellChanged = false; } void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order PositionVisitor posVisitor; cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) { try { addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->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) { MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); if (const auto object = mPhysics->getObject(ptr)) { navigator->removeObject(DetourNavigator::ObjectId(object)); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); } else if (mPhysics->getActor(ptr)) { navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(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 { try { mSceneManager->getTemplate(mMesh); } catch (std::exception&) { } } private: std::string mMesh; Resource::SceneManager* mSceneManager; }; 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())) mRendering.getWorkQueue()->addWorkItem(new PreloadMeshItem(mesh_, mRendering.getResourceSystem()->getSceneManager())); } void Scene::preloadCells(float dt) { if (dt<=1e-06) return; std::vector exteriorPositions; const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->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(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); else { osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3(); int x,y; MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,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 (!MWBase::Environment::get().getWorld()->isCellExterior()) return; int halfGridSizePlusOne = mHalfGridSize + 1; int cellX,cellY; cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y(); float centerX, centerY; MWBase::Environment::get().getWorld()->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; MWBase::Environment::get().getWorld()->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(MWBase::Environment::get().getWorld()->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(MWBase::Environment::get().getWorld()->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))); if (sync && mRendering.pagingUnlockCache()) mPreloader->abortTerrainPreloadExcept(nullptr); else mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); if (!sync) return; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); int progress = 0, initialProgress = -1, progressRange = 0; while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime())) { if (initialProgress == -1) { loadingListener->setLabel("#{sLoadingMessage4}"); initialProgress = progress; } if (progress) { loadingListener->setProgressRange(std::max(0, progressRange-initialProgress)); loadingListener->setProgress(progress-initialProgress); } else loadingListener->setProgress(0); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } 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 = MWBase::Environment::get().getWorld()->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(MWBase::Environment::get().getWorld()->getInterior(dest.mCellName)); else { osg::Vec3f pos = dest.mPos.asVec3(); int x,y; MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } } } openmw-openmw-0.47.0/apps/openmw/mwworld/scene.hpp000066400000000000000000000120411413061077700221670ustar00rootroot00000000000000#ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H #include #include #include "ptr.hpp" #include "globals.hpp" #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 MWWorld { class Player; class CellStore; class CellPreloader; enum class RotationOrder { direct, inverse }; class Scene { public: typedef std::set CellStoreCollection; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; bool mCellChanged; 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::set mPagedRefs; void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); 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); 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; public: Scene (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 unloadCell (CellStoreCollection::iterator iter, bool test = false); void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); 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, bool paused); void addObjectToScene (const Ptr& ptr); ///< Add an object that already exists in the world model to the scene. void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. 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.47.0/apps/openmw/mwworld/store.cpp000066400000000000000000001111211413061077700222200ustar00rootroot00000000000000#include "store.hpp" #include #include #include #include #include #include #include namespace { struct Compare { bool operator()(const ESM::Land *x, const ESM::Land *y) { if (x->mX == y->mX) { return x->mY < y->mY; } return x->mX < y->mX; } bool operator()(const ESM::Land *x, const std::pair& y) { if (x->mX == y.first) { return x->mY < y.second; } return x->mX < y.first; } }; } 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); mStatic.insert_or_assign(record.mIndex, 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) { const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); } 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 { std::string idLower = Misc::StringUtils::lowerCase(id); typename Dynamic::const_iterator dit = mDynamic.find(idLower); if (dit != mDynamic.end()) return &dit->second; typename std::map::const_iterator it = mStatic.find(idLower); if (it != mStatic.end()) return &(it->second); return nullptr; } template const T *Store::searchStatic(const std::string &id) const { std::string idLower = Misc::StringUtils::lowerCase(id); typename std::map::const_iterator it = mStatic.find(idLower); 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) 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())]; return nullptr; } template const T *Store::find(const std::string &id) const { const T *ptr = search(id); if (ptr == nullptr) { const std::string msg = T::getRecordType() + " '" + id + "' not found"; throw std::runtime_error(msg); } return ptr; } template RecordId Store::load(ESM::ESMReader &esm) { T record; bool isDeleted = false; record.load(esm, isDeleted); Misc::StringUtils::lowerCaseInPlace(record.mId); 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) { std::string id = Misc::StringUtils::lowerCase(item.mId); if(overrideOnly) { auto it = mStatic.find(id); if(it == mStatic.end()) return nullptr; } std::pair result = mDynamic.insert_or_assign(id, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template T *Store::insertStatic(const T &item) { std::string id = Misc::StringUtils::lowerCase(item.mId); std::pair result = mStatic.insert_or_assign(id, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template bool Store::eraseStatic(const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); typename std::map::iterator it = mStatic.find(idLower); 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((*sharedIter)->mId == idLower) { mShared.erase(sharedIter); break; } ++sharedIter; } mStatic.erase(it); } return true; } template bool Store::erase(const std::string &id) { std::string key = Misc::StringUtils::lowerCase(id); typename Dynamic::iterator it = mDynamic.find(key); if (it == mDynamic.end()) { return false; } mDynamic.erase(it); // have to reinit the whole shared part assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); for (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() { mStatic.emplace_back(); LandTextureList <exl = mStatic[0]; // More than enough to hold Morrowind.esm. Extra lists for plugins will we // added on-the-fly in a different method. ltexl.reserve(128); } 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, size_t plugin) { ESM::LandTexture lt; bool isDeleted = false; lt.load(esm, isDeleted); assert(plugin < mStatic.size()); // Replace texture for records with given ID and index from all plugins. for (unsigned int i=0; i(search(lt.mIndex, i)); if (tex) { const std::string texId = Misc::StringUtils::lowerCase(tex->mId); const std::string ltId = Misc::StringUtils::lowerCase(lt.mId); if (texId == ltId) { tex->mTexture = lt.mTexture; } } } LandTextureList <exl = mStatic[plugin]; if(lt.mIndex + 1 > (int)ltexl.size()) ltexl.resize(lt.mIndex+1); // Store it ltexl[lt.mIndex] = lt; return RecordId(lt.mId, isDeleted); } RecordId Store::load(ESM::ESMReader &esm) { return load(esm, esm.getIndex()); } 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(); } void Store::resize(size_t num) { if (mStatic.size() < num) mStatic.resize(num); } // Land //========================================================================= Store::~Store() { for (const ESM::Land* staticLand : mStatic) { delete staticLand; } } 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); std::vector::const_iterator it = std::lower_bound(mStatic.begin(), mStatic.end(), comp, Compare()); if (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 *ptr = new ESM::Land(); bool isDeleted = false; ptr->load(esm, isDeleted); // Same area defined in multiple plugins? -> last plugin wins // Can't use search() because we aren't sorted yet - is there any other way to speed this up? for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) { if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) { delete *it; mStatic.erase(it); break; } } mStatic.push_back(ptr); 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; std::sort(mStatic.begin(), mStatic.end(), Compare()); 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); } void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { //Handling MovedCellRefs, there is no way to do it inside loadcell while (esm.isNextSub("MVRF")) { ESM::CellRef ref; ESM::MovedCellRef cMRef; cell->getNextMVRF(esm, cMRef); ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. bool deleted = false; cell->getNextRef(esm, ref, deleted); // 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); } } const ESM::Cell *Store::search(const std::string &id) const { ESM::Cell cell; cell.mName = Misc::StringUtils::lowerCase(id); std::map::const_iterator it = mInt.find(cell.mName); if (it != mInt.end()) { return &(it->second); } DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); if (dit != mDynamicInt.end()) { return &dit->second; } return nullptr; } const ESM::Cell *Store::search(int x, int y) const { ESM::Cell cell; cell.mData.mX = x, cell.mData.mY = 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; } return nullptr; } const ESM::Cell *Store::searchStatic(int x, int y) const { ESM::Cell cell; cell.mData.mX = x, cell.mData.mY = y; std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); 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; 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); std::string idLower = Misc::StringUtils::lowerCase(cell.mName); 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(idLower)); 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[idLower] = 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"); } ESM::Cell *ptr; if (cell.isExterior()) { std::pair key(cell.getGridX(), cell.getGridY()); // duplicate insertions are avoided by search(ESM::Cell &) std::pair result = mDynamicExt.insert(std::make_pair(key, cell)); ptr = &result.first->second; mSharedExt.push_back(ptr); } else { std::string key = Misc::StringUtils::lowerCase(cell.mName); // duplicate insertions are avoided by search(ESM::Cell &) std::pair result = mDynamicInt.insert(std::make_pair(key, cell)); ptr = &result.first->second; mSharedInt.push_back(ptr); } return ptr; } 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) { std::string key = Misc::StringUtils::lowerCase(id); DynamicInt::iterator it = mDynamicInt.find(key); 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 = mCells->search(pathgrid.mCell) != nullptr; // 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 //========================================================================= template<> 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); } template <> 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); std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); std::map::iterator found = mStatic.find(idLower); if (found == mStatic.end()) { dialogue.loadData(esm, isDeleted); mStatic.insert(std::make_pair(idLower, dialogue)); } else { found->second.loadData(esm, isDeleted); dialogue = found->second; } return RecordId(dialogue.mId, isDeleted); } template<> bool Store::eraseStatic(const std::string &id) { auto it = mStatic.find(Misc::StringUtils::lowerCase(id)); if (it != mStatic.end()) mStatic.erase(it); return true; } } 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.47.0/apps/openmw/mwworld/store.hpp000066400000000000000000000272071413061077700222400ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_STORE_H #define OPENMW_MWWORLD_STORE_H #include #include #include #include "recordcmp.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; const T *find(int index) const; }; template class SharedIterator { typedef typename std::vector::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 { std::map mStatic; std::vector mShared; // 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::map mDynamic; typedef std::map Dynamic; typedef std::map Static; 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) const; 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; /// Resize the internal store to hold at least \a num plugins. void resize(size_t num); size_t getSize() const override; size_t getSize(size_t plugin) const; RecordId load(ESM::ESMReader &esm, size_t plugin); RecordId load(ESM::ESMReader &esm) override; iterator begin(size_t plugin) const; iterator end(size_t plugin) const; }; template <> class Store : public StoreBase { std::vector mStatic; public: typedef SharedIterator 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::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::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; 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; const ESM::WeaponType *find(const int id) const; RecordId load(ESM::ESMReader &esm) override { return RecordId(nullptr, false); } ESM::WeaponType* insert(const ESM::WeaponType &weaponType); void setUp() override; size_t getSize() const override; iterator begin() const; iterator end() const; }; } //end namespace #endif openmw-openmw-0.47.0/apps/openmw/mwworld/timestamp.cpp000066400000000000000000000051331413061077700230740ustar00rootroot00000000000000#include "timestamp.hpp" #include #include #include namespace MWWorld { TimeStamp::TimeStamp (float hour, int day) : mHour (hour), mDay (day) { if (hour<0 || hour>=24 || day<0) throw std::runtime_error ("invalid time stamp"); } 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"); hours += mHour; mHour = static_cast (std::fmod (hours, 24)); mDay += static_cast(hours / 24); 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.47.0/apps/openmw/mwworld/weather.cpp000066400000000000000000001444631413061077700225420ustar00rootroot00000000000000#include "weather.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.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 using 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; } } 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; 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" : "") , 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); if(Misc::Rng::rollProbability() <= 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. unsigned int distance = Misc::Rng::rollDice(4); // 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.mA); mChances.push_back(region.mData.mB); } 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). int chance = Misc::Rng::rollDice(100) + 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(0,1,0) , 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; float updatedSpeed = (Misc::Rng::rollClosedProbability() - 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"; if (mIsStorm) { osg::Vec3f stormDirection(0, 1, 0); if (mResult.mParticleEffect == "meshes\\ashcloud.nif" || mResult.mParticleEffect == "meshes\\blightcloud.nif") { osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3()); playerPos.z() = 0; osg::Vec3f redMountainPos (25000, 70000, 0); stormDirection = (playerPos - redMountainPos); stormDirection.normalize(); } mStormDirection = stormDirection; mRendering.getSkyManager()->setStormDirection(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 ); } 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); 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; } unsigned int WeatherManager::getWeatherID() const { return mCurrentWeather; } 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 = ""; 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; } 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.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) 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 - 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.47.0/apps/openmw/mwworld/weather.hpp000066400000000000000000000266051413061077700225440ustar00rootroot00000000000000#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: 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; // 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); unsigned int getWeatherID() const; 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.47.0/apps/openmw/mwworld/worldimp.cpp000066400000000000000000004312661413061077700227400ustar00rootroot00000000000000#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 "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.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 "../mwrender/animation.hpp" #include "../mwrender/npcanimation.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "../mwrender/vismask.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" namespace { // Wraps a value to (-PI, PI] void wrap(float& rad) { const float pi = static_cast(osg::PI); if (rad>0) rad = std::fmod(rad+pi, 2.0f*pi)-pi; else rad = std::fmod(rad-pi, 2.0f*pi)+pi; } } namespace MWWorld { struct GameContentLoader : public ContentLoader { GameContentLoader(Loading::Listener& listener) : ContentLoader(listener) { } bool addLoader(const std::string& extension, ContentLoader* loader) { return mLoaders.insert(std::make_pair(extension, loader)).second; } void load(const boost::filesystem::path& filepath, int& index) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { it->second->load(filepath, index); } else { std::string msg("Cannot load file: "); msg += filepath.string(); throw std::runtime_error(msg.c_str()); } } private: typedef std::map LoadersContainer; LoadersContainer mLoaders; }; 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, 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, mEsm), mSky (true), mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mShouldUpdateNavigator(false), mActivationDistanceOverride (activationDistanceOverride), mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { mEsm.resize(contentFiles.size() + groundcoverFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); GameContentLoader gameContentLoader(*listener); EsmLoader esmLoader(mStore, mEsm, encoder, *listener); gameContentLoader.addLoader(".esm", &esmLoader); gameContentLoader.addLoader(".esp", &esmLoader); gameContentLoader.addLoader(".omwgame", &esmLoader); gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); // insert records that may not be present in all versions of MW if (mEsm[0].getFormat() == 0) ensureNeededRecords(); mCurrentDate.reset(new DateTimeManager()); fillGlobalVariables(); mStore.setUp(true); mStore.movePlayerRecord(); mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager()) { navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp; navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope; navigatorSettings->mSwimHeightScale = mSwimHeightScale; DetourNavigator::RecastGlobalAllocator::init(); mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings)); } else { mNavigator.reset(new DetourNavigator::NavigatorStub()); } mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore)); mWorldScene.reset(new Scene(*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.reset(new MWWorld::WeatherManager(*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); } 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 } int World::countSavedGameCells() const { return mCells.countSavedGameRecords(); } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { // 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_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; 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; 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; 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; mStore.insertStatic(record); } } } World::~World() { // Must be cleared before mRendering is destroyed mProjectileManager->clear(); } 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; // 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); } void World::testExteriorCells() { mWorldScene->testExteriorCells(); } void World::testInteriorCells() { mWorldScene->testInteriorCells(); } void World::useDeathCamera() { if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) { mRendering->getCamera()->togglePreviewMode(false); mRendering->getCamera()->toggleVanityMode(false); } if(mRendering->getCamera()->isFirstPerson()) mRendering->getCamera()->toggleViewMode(true); } MWWorld::Player& World::getPlayer() { return *mPlayer; } const MWWorld::ESMStore& World::getStore() const { return mStore; } std::vector& World::getEsmReader() { return mEsm; } LocalScripts& World::getLocalScripts() { return mLocalScripts; } bool World::hasCellChanged() const { return mWorldScene->hasCellChanged(); } void World::setGlobalInt (const std::string& name, int value) { bool dateUpdated = mCurrentDate->updateGlobalInt(name, value); if (dateUpdated) updateSkyDate(); mGlobalVariables[name].setInteger (value); } void World::setGlobalFloat (const std::string& name, float value) { bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value); if (dateUpdated) updateSkyDate(); mGlobalVariables[name].setFloat(value); } int World::getGlobalInt (const std::string& name) const { return mGlobalVariables[name].getInteger(); } float World::getGlobalFloat (const std::string& name) const { return mGlobalVariables[name].getFloat(); } char World::getGlobalVariableType (const std::string& 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 (const std::string& 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 (const std::string& name, bool activeOnly) { Ptr ret = searchPtr(name, activeOnly); if (!ret.isEmpty()) return ret; std::string error = "failed to find an instance of object '" + 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() (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.getTypeName()==typeid (ESM::Container).name() || reference.getTypeName()==typeid (ESM::NPC).name() || reference.getTypeName()==typeid (ESM::Creature).name()) { 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) { // enable is a no-op for items in containers 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.getTypeName()==typeid (ESM::Container).name() || reference.getTypeName()==typeid (ESM::NPC).name() || reference.getTypeName()==typeid (ESM::Creature).name()) { 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"); 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); } 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(); } 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 && 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 () { 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, float x, float y, float z, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = z; ptr.getRefData().setPosition(pos); osg::Vec3f vec(x, y, z); 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); 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); 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, vec, movePhysics); if (movePhysics) { if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } } if (isPlayer) mWorldScene->playerMoved(vec); else { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(newPtr); } return newPtr; } MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) { int cellX, cellY; positionToIndex(x, y, cellX, cellY); CellStore* cell = ptr.getCell(); CellStore* newCell = getExterior(cellX, cellY); bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) cell = newCell; return moveObject(ptr, cell, x, y, z, movePhysics); } MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) { auto* actor = mPhysics->getActor(ptr); osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; if (actor) actor->adjustPosition(vec, ignoreCollisions); if (ptr.getClass().isActor()) return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); } void World::scaleObject (const Ptr& ptr, float scale) { if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); if (scale != ptr.getCellRef().getScale()) { 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(getPathfindingHalfExtents(ptr)); else if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) { const float pi = static_cast(osg::PI); 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. */ const float half_pi = pi/2.f; if(objRot[0] < -half_pi) objRot[0] = -half_pi; else if(objRot[0] > half_pi) objRot[0] = half_pi; wrap(objRot[1]); wrap(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.x(), pos.y(), pos.z()); } 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::rotateObject (const Ptr& ptr, float x, float y, float z, MWBase::RotationFlags flags) { rotateObjectImp(ptr, osg::Vec3f(x, y, z), flags); } void World::rotateWorldObject (const Ptr& ptr, 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); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } } MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, 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::positionToIndex (float x, float y, int &cellX, int &cellY) const { cellX = static_cast(std::floor(x / Constants::CellSizeInUnits)); cellY = static_cast(std::floor(y / Constants::CellSizeInUnits)); } 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) { mPhysics->stepSimulation(); processDoors(duration); mProjectileManager->update(duration); const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); mProjectileManager->processHits(); mDiscardMovements = false; for(const auto& actor : results) { // Handle player last, in case a cell transition occurs if(actor != getPlayerPtr()) { auto* physactor = mPhysics->getActor(actor); assert(physactor); const auto position = physactor->getSimulationPosition(); moveObject(actor, position.x(), position.y(), position.z(), false, false); } } const auto player = std::find(results.begin(), results.end(), getPlayerPtr()); if (player != results.end()) { auto* physactor = mPhysics->getActor(*player); assert(physactor); const auto position = physactor->getSimulationPosition(); moveObject(*player, position.x(), position.y(), position.z(), false, false); } } void World::updateNavigator() { mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) updateNavigatorObject(*object); if (mShouldUpdateNavigator) { mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); mShouldUpdateNavigator = false; } } void World::updateNavigatorObject(const MWPhysics::Object& object) { const DetourNavigator::ObjectShapes shapes(object.getShapeInstance()); 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(); float oldRot = objPos.rot[2]; 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::min(std::max(minRot, oldRot + diff), maxRot); rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, 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, objPos.rot[0], objPos.rot[1], 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(); } updatePlayer(); mPhysics->debugDraw(); mWorldScene->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::updatePlayer() { MWWorld::Ptr player = getPlayerPtr(); // TODO: move to MWWorld::Player if (player.getCell()->isExterior()) { ESM::Position pos = player.getRefData().getPosition(); mPlayer->setLastKnownExteriorPosition(pos.asVec3()); } bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf(); bool isFirstPerson = mRendering->getCamera()->isFirstPerson(); if (isWerewolf && isFirstPerson) { float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); if (werewolfFov != 0) mRendering->overrideFieldOfView(werewolfFov); MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true); } else { mRendering->resetFieldOfView(); MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false); } // Sink the camera while sneaking bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool swimming = isSwimming(player); bool flying = isFlying(player); static const float i1stPersonSneakDelta = mStore.get().find("i1stPersonSneakDelta")->mValue.getFloat(); if (sneaking && !swimming && !flying) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else mRendering->getCamera()->setSneakOffset(0.f); int blind = 0; auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); if (!mGodMode) blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); } 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() { const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); osg::Vec3f listenerPos; if (isFirstPerson()) listenerPos = mRendering->getCameraPosition(); 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(), mRendering->getCameraPosition()); 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.hasContentFile()) { 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::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(); } 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 { GetDoorMarkerVisitor(std::vector& out) : mOut(out) { } 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; MWBase::Environment::get().getWorld()->positionToIndex( ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1], cellid.mIndex.mX, cellid.mIndex.mY); } 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()) { int cellX, cellY; positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY); cell = mCells.getExterior(cellX, cellY); } 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.pos[0], pos.pos[1], pos.pos[2]); } } 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()->isFirstPerson(); } bool World::isPreviewModeEnabled() const { return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview; } void World::togglePreviewMode(bool enable) { mRendering->getCamera()->togglePreviewMode(enable); } 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); } void World::allowVanityMode(bool allow) { mRendering->getCamera()->allowVanityMode(allow); } bool World::vanityRotateCamera(float * rot) { if(!mRendering->getCamera()->isVanityOrPreviewModeEnabled()) return false; mRendering->getCamera()->rotateCamera(rot[0], rot[2], true); return true; } void World::adjustCameraDistance(float dist) { mRendering->getCamera()->adjustCameraDistance(dist); } void World::saveLoaded() { mStore.validateDynamic(); } void World::setupPlayer() { const ESM::NPC *player = mStore.get().find("player"); if (!mPlayer) mPlayer.reset(new MWWorld::Player(player)); else { // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); mPlayer->set(player); } Ptr ptr = mPlayer->getPlayer(); mRendering->setupPlayer(ptr); } void World::renderPlayer() { MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); 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()); // apply race height rotateObject(player, 0.f, 0.f, 0.f, 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); mDefaultHalfExtents = mPhysics->getOriginalHalfExtents(getPlayerPtr()); mNavigator->addAgent(getPathfindingHalfExtents(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()) { int x, y; ESM::Position doorDest = door->getDoorDest(); positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y); source = getExterior(x, 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, const std::vector& groundcover, ContentLoader& contentLoader) { 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)) { contentLoader.load(col.getPath(file), idx); } else { std::string message = "Failed loading " + file + ": the content file does not exist"; throw std::runtime_error(message); } idx++; } ESM::GroundcoverIndex = idx; for (const std::string &file : groundcover) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { contentLoader.load(col.getPath(file), idx); } else { std::string message = "Failed loading " + file + ": the groundcover file does not exist"; throw std::runtime_error(message); } idx++; } } bool World::startSpellCast(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); std::string message; bool fail = false; bool isPlayer = (actor == getPlayerPtr()); std::string selectedSpell = stats.getSpells().getSelectedSpell(); if (!selectedSpell.empty()) { const ESM::Spell* spell = mStore.get().find(selectedSpell); // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; } // If this is a power, check if it was already used in the last 24h if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell)) { message = "#{sPowerAlreadyUsed}"; fail = true; } // Reduce mana if (!fail && !godmode) { magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); stats.setMagicka(magicka); } } if (isPlayer && fail) MWBase::Environment::get().getWindowManager()->messageBox(message); return !fail; } 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()) cast.cast(*inv.getSelectedEnchantItem()); } } 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 = MWBase::Environment::get().getWorld()->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) { mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } void World::updateProjectilesCasters() { mProjectileManager->updateCasters(); } class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor { private: MWWorld::Ptr mActor; public: ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) : mActor(actor) { } void visit (MWMechanics::EffectKey key, int /*effectIndex*/, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override { const ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const auto magicEffect = store.get().find(key.mId); if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0) return; const ESM::Static* castStatic; if (!magicEffect->mHit.empty()) castStatic = store.get().find (magicEffect->mHit); else castStatic = store.get().find ("VFX_DefaultHit"); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); if (anim && !castStatic->mModel.empty()) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle); } }; void World::applyLoopingParticles(const MWWorld::Ptr& ptr) { const MWWorld::Class &cls = ptr.getClass(); if (cls.isActor()) { ApplyLoopingParticlesVisitor visitor(ptr); cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor); cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor); if (cls.hasInventoryStore(ptr)) cls.getInventoryStore(ptr).visitEffectSources(visitor); } } const std::vector& World::getContentFiles() const { return mContentFiles; } void World::breakInvisibility(const Ptr &actor) { actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility); actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(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 { AddDetectedReferenceVisitor(std::vector& out, const Ptr& detector, World::DetectionType type, float squaredDist) : mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type) { } std::vector& mOut; Ptr mDetector; float mSquaredDist; World::DetectionType mType; 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().getTypeName() == typeid(ESM::Container).name(); if (mType != World::Detect_Creature && (ptr.getClass().isActor() || isContainer)) { // but ignore containers without resolved content if (isContainer && ptr.getRefData().getCustomData() == nullptr) 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().getTypeName() != typeid(ESM::NPC).name()) return false; } else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) 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, type, dist*dist); 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() { 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; MWWorld::Ptr player = getPlayerPtr(); 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 { 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); // [1, iNumberCreatures] for (int i=0; ispawnEffect(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) { std::map > toApply; for (const ESM::ENAMstruct& effectInfo : effects.mList) { 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("meshes\\" + areaStatic->mModel, texture, origin, 1.0f); continue; } else mRendering->spawnEffect("meshes\\" + areaStatic->mModel, 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; toApply[affected].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.mStack = false; ESM::EffectList effectsToApply; effectsToApply.mList = applyPair.second; cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); } } void World::activate(const Ptr &object, const Ptr &actor) { breakInvisibility(actor); if (object.getRefData().activate()) { std::shared_ptr action = (object.getClass().activate(object, actor)); action->execute (actor); } } struct ResetActorsVisitor { bool operator() (Ptr ptr) { if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile()) { if (ptr.getCell()->movedHere(ptr)) return true; const ESM::Position& origPos = ptr.getCellRef().getPosition(); MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]); MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]); ptr.getClass().adjustPosition(ptr, true); } return true; } }; void World::resetActors() { for (CellStore* cellstore : mWorldScene->getActiveCells()) { ResetActorsVisitor visitor; 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 osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const { mRendering->updateActorPath(actor, path, halfExtents, start, end); } void World::removeActorPath(const MWWorld::ConstPtr& actor) const { mRendering->removeActorPath(actor); } void World::setNavMeshNumberToRender(const std::size_t value) { mRendering->setNavMeshNumber(value); } osg::Vec3f World::getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const { if (actor.isInCell() && actor.getCell()->isExterior()) return mDefaultHalfExtents; // Using default half extents for better performance else return getHalfExtents(actor); } 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()->getCollisionShape()->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 MWWorld::ConstPtr& ignore) const { return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); } 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); } } openmw-openmw-0.47.0/apps/openmw/mwworld/worldimp.hpp000066400000000000000000001024101413061077700227270ustar00rootroot00000000000000#ifndef GAME_MWWORLD_WORLDIMP_H #define GAME_MWWORLD_WORLDIMP_H #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" namespace osg { class Group; class Stats; } namespace osgViewer { class Viewer; } namespace Resource { class ResourceSystem; } namespace SceneUtil { class WorkQueue; } namespace ESM { struct Position; } namespace Files { class Collections; } namespace MWRender { class SkyManager; class Animation; class Camera; } 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; std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; 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::shared_ptr mProjectileManager; bool mSky; bool mGodMode; bool mScriptsEnabled; bool mDiscardMovements; std::vector mContentFiles; std::string mUserDataPath; osg::Vec3f mDefaultHalfExtents; 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 // not implemented World (const World&); World& operator= (const World&); void updateWeather(float duration, bool paused = false); void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); void updatePlayer(); 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(); /** * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) * @param fileCollections- Container which holds content file names and their paths * @param content - Container which holds content file names * @param contentLoader - */ void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); 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, 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 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; 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, osg::Quat rotate) override; bool toggleWater() override; bool toggleWorld() override; bool toggleBorders() override; void adjustSky() override; Player& getPlayer() override; MWWorld::Ptr getPlayerPtr() override; MWWorld::ConstPtr getPlayerConstPtr() const override; const MWWorld::ESMStore& getStore() const override; std::vector& getEsmReader() 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 (const std::string& name, int value) override; ///< Set value independently from real type. void setGlobalFloat (const std::string& name, float value) override; ///< Set value independently from real type. int getGlobalInt (const std::string& name) const override; ///< Get value independently from real type. float getGlobalFloat (const std::string& name) const override; ///< Get value independently from real type. char getGlobalVariableType (const std::string& 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 (const std::string& name, bool activeOnly) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. Ptr searchPtr (const std::string& 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; 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; 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, float x, float y, float z, 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, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) 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, float x, float y, float z, MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override; MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, 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() override; void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const override; ///< Convert cell numbers to position. void positionToIndex (float x, float y, int &cellX, int &cellY) const override; ///< Convert position to cell numbers 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; 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) override; void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) override; void updateWindowManager () override; 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; void togglePreviewMode(bool enable) override; bool toggleVanityMode(bool enable) override; void allowVanityMode(bool allow) override; bool vanityRotateCamera(float * rot) override; void adjustCameraDistance(float dist) 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) override; /// \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 true if the spell can be casted (i.e. the animation should start) */ bool 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) 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) 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) 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 osg::Vec3f& halfExtents, 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; /// Return physical half extents of the given actor to be used in pathfinding osg::Vec3f getPathfindingHalfExtents(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 MWWorld::ConstPtr& ignore) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; std::vector getAll(const std::string& id) override; }; } #endif openmw-openmw-0.47.0/apps/openmw_test_suite/000077500000000000000000000000001413061077700211405ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/CMakeLists.txt000066400000000000000000000035601413061077700237040ustar00rootroot00000000000000find_package(GTest 1.10 REQUIRED) find_package(GMock 1.10 REQUIRED) if (GTEST_FOUND AND GMOCK_FOUND) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp mwworld/test_store.cpp mwdialogue/test_keywordsearch.cpp esm/test_fixed_string.cpp esm/variant.cpp misc/test_stringops.cpp misc/test_endianness.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 settings/parser.cpp shader/parsedefines.cpp shader/parsefors.cpp shader/shadermanager.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 ${GMOCK_LIBRARIES} 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() if (MSVC) if (CMAKE_CL_64) # Debug version of openmw_unit_tests needs increased number of sections beyond 2^16 # just like openmw and openmw-cs set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") endif (CMAKE_CL_64) endif (MSVC) endif() openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/000077500000000000000000000000001413061077700243555ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/gettilespositions.cpp000066400000000000000000000064261413061077700306610ustar00rootroot00000000000000#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 { Settings 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(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), 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(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), 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(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), 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(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), 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(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } } openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/navigator.cpp000066400000000000000000001446761413061077700270750ustar00rootroot00000000000000#include "operators.hpp" #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-4 && std::abs(arg.y() - y) < 1e-4 && std::abs(arg.z() - z) < 1e-4; } namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorNavigatorTest : Test { Settings mSettings; std::unique_ptr mNavigator; osg::Vec3f mPlayerPosition; osg::Vec3f mAgentHalfExtents; osg::Vec3f mStart; osg::Vec3f mEnd; std::deque mPath; std::back_insert_iterator> mOut; float mStepSize; AreaCosts mAreaCosts; Loading::Listener mListener; DetourNavigatorNavigatorTest() : mPlayerPosition(0, 0, 0) , mAgentHalfExtents(29, 29, 66) , mStart(-204, 204, 1) , mEnd(204, -204, 1) , mOut(mPath) , mStepSize(28.333332061767578125f) { mSettings.mEnableWriteRecastMeshToFile = false; mSettings.mEnableWriteNavMeshToFile = false; mSettings.mEnableRecastMeshFileNameRevision = false; mSettings.mEnableNavMeshFileNameRevision = false; mSettings.mBorderSize = 16; mSettings.mCellHeight = 0.2f; mSettings.mCellSize = 0.2f; mSettings.mDetailSampleDist = 6; mSettings.mDetailSampleMaxError = 1; mSettings.mMaxClimb = 34; mSettings.mMaxSimplificationError = 1.3f; mSettings.mMaxSlope = 49; mSettings.mRecastScaleFactor = 0.017647058823529415f; mSettings.mSwimHeightScale = 0.89999997615814208984375f; mSettings.mMaxEdgeLen = 12; mSettings.mMaxNavMeshQueryNodes = 2048; mSettings.mMaxVertsPerPoly = 6; mSettings.mRegionMergeSize = 20; mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; mSettings.mMaxPolys = 4096; mSettings.mMaxTilesNumber = 512; mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); mNavigator.reset(new NavigatorImpl(mSettings)); } }; 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 osg::ref_ptr makeBulletShapeInstance(std::unique_ptr&& shape) { osg::ref_ptr bulletShape(new Resource::BulletShape); bulletShape->mCollisionShape = 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; }; TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) { mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204.0000152587890625, 204, 1.99998295307159423828125), Vec3fEq(-183.96533203125, 183.9653167724609375, 1.99998819828033447265625), Vec3fEq(-163.930633544921875, 163.9306182861328125, 1.99999344348907470703125), Vec3fEq(-143.8959503173828125, 143.89593505859375, -2.720611572265625), Vec3fEq(-123.86126708984375, 123.86124420166015625, -13.1089687347412109375), Vec3fEq(-103.82657623291015625, 103.8265533447265625, -23.497333526611328125), Vec3fEq(-83.7918853759765625, 83.7918548583984375, -33.885692596435546875), Vec3fEq(-63.757190704345703125, 63.757171630859375, -44.274051666259765625), Vec3fEq(-43.722503662109375, 43.72248077392578125, -54.66241455078125), Vec3fEq(-23.687808990478515625, 23.6877918243408203125, -65.05077362060546875), Vec3fEq(-3.6531188488006591796875, 3.6531002521514892578125, -75.43914031982421875), Vec3fEq(16.3815746307373046875, -16.381591796875, -69.74927520751953125), Vec3fEq(36.416263580322265625, -36.416286468505859375, -60.4739532470703125), Vec3fEq(56.450958251953125, -56.450977325439453125, -51.1986236572265625), Vec3fEq(76.48564910888671875, -76.4856719970703125, -41.92330169677734375), Vec3fEq(96.5203399658203125, -96.52036285400390625, -31.46941375732421875), Vec3fEq(116.55503082275390625, -116.5550537109375, -19.597003936767578125), Vec3fEq(136.5897216796875, -136.5897369384765625, -7.724592685699462890625), Vec3fEq(156.624420166015625, -156.624420166015625, 1.99999535083770751953125), Vec3fEq(176.6591033935546875, -176.65911865234375, 1.99999010562896728515625), Vec3fEq(196.69378662109375, -196.6938018798828125, 1.99998486042022705078125), Vec3fEq(204, -204.0000152587890625, 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 auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), Vec3fEq(204, -204, 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 auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_should_use_higher) { 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; 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, }}; const auto shapePtr2 = makeSquareHeightfieldTerrainShape(heightfieldData2); btHeightfieldTerrainShape& shape2 = *shapePtr2; shape2.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&shape2), nullptr, shape2, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.999981403350830078125), Vec3fEq(-183.965301513671875, 183.965301513671875, -0.428465187549591064453125), Vec3fEq(-163.9306182861328125, 163.9306182861328125, -2.8569104671478271484375), Vec3fEq(-143.89593505859375, 143.89593505859375, -5.28535556793212890625), Vec3fEq(-123.86124420166015625, 123.86124420166015625, -7.7138004302978515625), Vec3fEq(-103.8265533447265625, 103.8265533447265625, -10.142246246337890625), Vec3fEq(-83.7918548583984375, 83.7918548583984375, -12.3704509735107421875), Vec3fEq(-63.75716400146484375, 63.75716400146484375, -14.354084014892578125), Vec3fEq(-43.72247314453125, 43.72247314453125, -16.3377170562744140625), Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -18.32135009765625), Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -20.3049831390380859375), Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -19.044734954833984375), Vec3fEq(36.416290283203125, -36.416290283203125, -17.061100006103515625), Vec3fEq(56.450984954833984375, -56.450984954833984375, -15.0774688720703125), Vec3fEq(76.4856719970703125, -76.4856719970703125, -13.0938358306884765625), Vec3fEq(96.52036285400390625, -96.52036285400390625, -11.02784252166748046875), Vec3fEq(116.5550537109375, -116.5550537109375, -8.5993976593017578125), Vec3fEq(136.5897369384765625, -136.5897369384765625, -6.170953273773193359375), Vec3fEq(156.6244354248046875, -156.6244354248046875, -3.74250507354736328125), Vec3fEq(176.6591339111328125, -176.6591339111328125, -1.314060688018798828125), Vec3fEq(196.693817138671875, -196.693817138671875, 1.1143856048583984375), Vec3fEq(204, -204, 1.9999811649322509765625) )) << mPath; } 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, }}; auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); shapePtr->setLocalScaling(btVector3(128, 128, 1)); bulletShape->mCollisionShape = 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, }}; auto shapeAvoidPtr = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); shapeAvoidPtr->setLocalScaling(btVector3(128, 128, 1)); bulletShape->mAvoidCollisionShape = shapeAvoidPtr.release(); osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(instance->getCollisionShape()), ObjectShapes(instance), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99997997283935546875), Vec3fEq(-191.328948974609375, 178.65789794921875, -0.815807759761810302734375), Vec3fEq(-178.65789794921875, 153.3157806396484375, -3.6315968036651611328125), Vec3fEq(-165.986846923828125, 127.9736785888671875, -6.4473857879638671875), Vec3fEq(-153.3157806396484375, 102.6315765380859375, -9.26317310333251953125), Vec3fEq(-140.6447296142578125, 77.28946685791015625, -12.07896137237548828125), Vec3fEq(-127.9736785888671875, 51.947368621826171875, -14.894748687744140625), Vec3fEq(-115.3026275634765625, 26.6052646636962890625, -17.7105388641357421875), Vec3fEq(-102.63158416748046875, 1.2631585597991943359375, -20.5263233184814453125), Vec3fEq(-89.9605712890625, -24.0789661407470703125, -19.591716766357421875), Vec3fEq(-68.54410552978515625, -42.629238128662109375, -19.847625732421875), Vec3fEq(-47.127635955810546875, -61.17951202392578125, -20.1035366058349609375), Vec3fEq(-25.711170196533203125, -79.72978973388671875, -20.359447479248046875), Vec3fEq(-4.294706821441650390625, -98.280059814453125, -20.6153545379638671875), Vec3fEq(17.121753692626953125, -116.83034515380859375, -17.3710460662841796875), Vec3fEq(42.7990570068359375, -128.80755615234375, -14.7094440460205078125), Vec3fEq(68.4763641357421875, -140.7847747802734375, -12.0478420257568359375), Vec3fEq(94.15366363525390625, -152.761993408203125, -9.3862361907958984375), Vec3fEq(119.83097076416015625, -164.7392120361328125, -6.724635601043701171875), Vec3fEq(145.508270263671875, -176.7164306640625, -4.06303119659423828125), Vec3fEq(171.185577392578125, -188.69366455078125, -1.40142619609832763671875), Vec3fEq(196.862884521484375, -200.6708831787109375, 1.2601754665374755859375), Vec3fEq(204, -204, 1.999979496002197265625) )) << 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mStart.z() = 300; mEnd.x() = 0; mEnd.z() = 300; EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, 185.33331298828125), Vec3fEq(0, 175.6666717529296875, 185.33331298828125), Vec3fEq(0, 147.3333282470703125, 185.33331298828125), Vec3fEq(0, 119, 185.33331298828125), Vec3fEq(0, 90.6666717529296875, 185.33331298828125), Vec3fEq(0, 62.333339691162109375, 185.33331298828125), Vec3fEq(0, 34.00000762939453125, 185.33331298828125), Vec3fEq(0, 5.66667461395263671875, 185.33331298828125), Vec3fEq(0, -22.6666584014892578125, 185.33331298828125), Vec3fEq(0, -50.999988555908203125, 185.33331298828125), Vec3fEq(0, -79.33332061767578125, 185.33331298828125), Vec3fEq(0, -107.666656494140625, 185.33331298828125), Vec3fEq(0, -135.9999847412109375, 185.33331298828125), Vec3fEq(0, -164.33331298828125, 185.33331298828125), Vec3fEq(0, -192.666656494140625, 185.33331298828125), Vec3fEq(0, -204, 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, -98.000030517578125), Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), Vec3fEq(0, 147.3333282470703125, -118.6060791015625), Vec3fEq(0, 119, -128.90911865234375), Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), Vec3fEq(0, -107.666656494140625, -133.0303192138671875), Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), Vec3fEq(0, -164.33331298828125, -112.4242706298828125), Vec3fEq(0, -192.666656494140625, -102.12123870849609375), Vec3fEq(0, -204, -98.00002288818359375) )) << 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits::max(), -25, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, -98.000030517578125), Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), Vec3fEq(0, 147.3333282470703125, -118.6060791015625), Vec3fEq(0, 119, -128.90911865234375), Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), Vec3fEq(0, -107.666656494140625, -133.0303192138671875), Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), Vec3fEq(0, -164.33331298828125, -112.4242706298828125), Vec3fEq(0, -192.666656494140625, -102.12123870849609375), Vec3fEq(0, -204, -98.00002288818359375) )) << 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, -98.000030517578125), Vec3fEq(10.26930999755859375, 177.59320068359375, -107.4711456298828125), Vec3fEq(20.5386199951171875, 151.1864166259765625, -116.9422607421875), Vec3fEq(30.8079280853271484375, 124.77960968017578125, -126.41339111328125), Vec3fEq(41.077239990234375, 98.37281036376953125, -135.8845062255859375), Vec3fEq(51.346546173095703125, 71.96601104736328125, -138.2003936767578125), Vec3fEq(61.615856170654296875, 45.559215545654296875, -140.0838470458984375), Vec3fEq(71.88516998291015625, 19.1524181365966796875, -141.9673004150390625), Vec3fEq(82.15447235107421875, -7.254379749298095703125, -142.3074798583984375), Vec3fEq(81.04636383056640625, -35.56603240966796875, -142.7104339599609375), Vec3fEq(79.93825531005859375, -63.877685546875, -143.1133880615234375), Vec3fEq(78.83014678955078125, -92.18933868408203125, -138.7660675048828125), Vec3fEq(62.50392913818359375, -115.3460235595703125, -130.237823486328125), Vec3fEq(46.17771148681640625, -138.502716064453125, -121.8172149658203125), Vec3fEq(29.85149383544921875, -161.6594085693359375, -113.39659881591796875), Vec3fEq(13.52527523040771484375, -184.81610107421875, -104.97599029541015625), Vec3fEq(0, -204, -98.00002288818359375) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->removeObject(ObjectId(&shape)); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), Vec3fEq(204, -204, 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, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); Misc::Rng::init(42); const auto result = mNavigator->findRandomPointAroundCircle(mAgentHalfExtents, mStart, 100.0, Flag_walk); ASSERT_THAT(result, Optional(Vec3fEq(-198.909332275390625, 123.06096649169921875, 1.99998414516448974609375))) << (result ? *result : osg::Vec3f()); const auto distance = (*result - mStart).length(); EXPECT_FLOAT_EQ(distance, 81.105133056640625) << distance; } TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) { mSettings.mAsyncNavMeshUpdaterThreads = 2; mNavigator.reset(new NavigatorImpl(mSettings)); 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 auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); std::vector> boxes; std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), 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(i * 10 + 1, i * 10 + 1, i * 10 + 1)); mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), Vec3fEq(-189.9427337646484375, 179.3997802734375, 1.9999866485595703125), Vec3fEq(-175.8854522705078125, 154.7995452880859375, 1.99999034404754638671875), Vec3fEq(-161.82818603515625, 130.1993255615234375, -3.701923847198486328125), Vec3fEq(-147.770904541015625, 105.5991058349609375, -15.67664432525634765625), Vec3fEq(-133.7136383056640625, 80.99887847900390625, -27.6513614654541015625), Vec3fEq(-119.65636444091796875, 56.39865875244140625, -20.1209163665771484375), Vec3fEq(-105.59909820556640625, 31.798435211181640625, -25.0669879913330078125), Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.1782474517822265625), Vec3fEq(53.413402557373046875, -120.6742401123046875, -19.4096889495849609375), Vec3fEq(78.20446014404296875, -134.39215087890625, -27.6632633209228515625), Vec3fEq(102.99552154541015625, -148.110076904296875, -15.8613681793212890625), Vec3fEq(127.7865753173828125, -161.827972412109375, -4.059485912322998046875), Vec3fEq(152.57763671875, -175.5458984375, 1.9999904632568359375), Vec3fEq(177.3686981201171875, -189.2638092041015625, 1.9999866485595703125), Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.9999830722808837890625), Vec3fEq(204, -204, 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(mAgentHalfExtents); 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()), 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()), 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()), 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 auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk); ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.99998295307159423828125))) << (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 auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(32, 32, 400); CollisionShapeInstance boderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations mNavigator->addObject(ObjectId(&boderBox.shape()), ObjectShapes(boderBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); { const auto navMesh = navMeshes.begin()->second->lockConst(); ASSERT_EQ(navMesh->getGeneration(), 1); ASSERT_EQ(navMesh->getNavMeshRevision(), 4); } 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()), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } ASSERT_EQ(navMeshes.size(), 1); { const auto navMesh = navMeshes.begin()->second->lockConst(); ASSERT_EQ(navMesh->getGeneration(), 1); ASSERT_EQ(navMesh->getNavMeshRevision(), 4); } } } openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp000066400000000000000000000451211413061077700305520ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include namespace DetourNavigator { static inline bool operator ==(const NavMeshDataRef& lhs, const NavMeshDataRef& rhs) { return std::make_pair(lhs.mValue, lhs.mSize) == std::make_pair(rhs.mValue, rhs.mSize); } } namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorNavMeshTilesCacheTest : Test { const osg::Vec3f mAgentHalfExtents {1, 2, 3}; const TilePosition mTilePosition {0, 0}; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; const std::vector mIndices {{0, 1, 2}}; const std::vector mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; const std::vector mAreaTypes {1, AreaType_ground}; const std::vector mWater {}; const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mWater}; const std::vector mOffMeshConnections {}; unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mNavMeshData {mData, 1}; const size_t cRecastMeshKeySize = mRecastMesh.getIndices().size() * sizeof(int) + mRecastMesh.getVertices().size() * sizeof(float) + mRecastMesh.getAreaTypes().size() * sizeof(AreaType) + mRecastMesh.getWater().size() * sizeof(RecastMesh::Water) + mOffMeshConnections.size() * sizeof(OffMeshConnection); const size_t cRecastMeshWithWaterKeySize = cRecastMeshKeySize + sizeof(RecastMesh::Water); }; TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) { const std::size_t maxSize = 0; NavMeshTilesCache cache(maxSize); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } 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(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData))); EXPECT_NE(mNavMeshData.mValue, nullptr); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_return_cached_element) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_EQ(mNavMeshData.mValue, nullptr); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); ASSERT_TRUE(result); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); } 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 osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } 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(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections)); } 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, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1})); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData))); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, leastRecentlySetWater}; const auto leastRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1}; const std::vector mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mostRecentlySetWater}; const auto mostRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1}; ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections, std::move(leastRecentlySetNavMeshData))); ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections, std::move(mostRecentlySetNavMeshData))); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater}; const auto leastRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; const std::vector mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater}; const auto mostRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections, std::move(leastRecentlyUsedNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections, std::move(mostRecentlyUsedNavMeshData)); { const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections); ASSERT_TRUE(value); ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1})); } { const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections); ASSERT_TRUE(value); ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1})); } const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections)); } 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 navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, std::move(tooLargeNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } 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 navMeshDataSize = 1; const std::size_t navMeshKeySize1 = cRecastMeshKeySize; const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * navMeshDataSize + navMeshKeySize1 + navMeshKeySize2; NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; const std::vector tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, tooLargeWater}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); ASSERT_TRUE(value); ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData))); EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, std::move(tooLargeNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); ASSERT_TRUE(firstCopy); { const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); ASSERT_TRUE(secondCopy); } EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); ASSERT_TRUE(firstCopy); { const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); ASSERT_TRUE(secondCopy); } EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); } } openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/operators.hpp000066400000000000000000000051431413061077700271070ustar00rootroot00000000000000#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H #define OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H #include #include #include #include #include #include #include #include namespace DetourNavigator { static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) { return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax; } } 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) << "osg::Vec3f(" << 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.47.0/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp000066400000000000000000000453061413061077700305760ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { static inline bool operator ==(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) { return lhs.mCellSize == rhs.mCellSize && lhs.mTransform == rhs.mTransform; } } namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorRecastMeshBuilderTest : Test { Settings mSettings; TileBounds mBounds; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; DetourNavigatorRecastMeshBuilderTest() { mSettings.mRecastScaleFactor = 1.0f; 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(mSettings, mBounds); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, -1, 0, -1, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0, 0.5, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 2, 1, 3})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, -1, 2, 1, 1, 2, -1, -1, 2, -1, 1, -2, 1, -1, -2, 1, 1, -2, -1, -1, -2, -1, })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ 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, })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -1, -2, -1, -1, -2, 1, -1, 0, -1, -1, 0, 1, -1, 2, -1, -1, 2, 1, 1, -2, -1, 1, -2, 1, 1, 0, -1, 1, 0, 1, 1, 2, -1, 1, 2, 1, })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ 8, 3, 2, 11, 10, 4, 4, 5, 11, 11, 7, 6, 6, 10, 11, 11, 5, 1, 1, 7, 11, 0, 1, 5, 5, 4, 0, 0, 4, 10, 10, 6, 0, 0, 6, 7, 7, 1, 0, 8, 3, 9, })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 3, 12, 2, 1, 12, 10, 1, 12, 2, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, -1, 0, -1, -2, 0, -3, -3, 0, -2, -3, 0, -3, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(2, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_bhv_triangle_shape_should_filter_by_bounds) { mSettings.mRecastScaleFactor = 0.1f; mBounds.mMin = osg::Vec2f(-3, -3) * mSettings.mRecastScaleFactor; mBounds.mMax = osg::Vec2f(-2, -2) * mSettings.mRecastScaleFactor; 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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.2f, 0, -0.3f, -0.3f, 0, -0.2f, -0.3f, 0, -0.3f, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ 0, -0.70710659027099609375, -3.535533905029296875, 0, 0.707107067108154296875, -3.535533905029296875, 0, 2.384185791015625e-07, -4.24264049530029296875, }))); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ -3.535533905029296875, -0.70710659027099609375, 0, -3.535533905029296875, 0.707107067108154296875, 0, -4.24264049530029296875, 2.384185791015625e-07, 0, }))); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), AreaType_ground ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ 1.41421353816986083984375, 0, 1.1920928955078125e-07, -1.41421353816986083984375, 0, -1.1920928955078125e-07, 1.1920928955078125e-07, 0, -1.41421353816986083984375, }))); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); EXPECT_EQ(recastMesh->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(mSettings, mBounds); builder.addObject( static_cast(shape1), btTransform::getIdentity(), AreaType_ground ); builder.addObject( static_cast(shape2), btTransform::getIdentity(), AreaType_null ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, -1, 0, -1, -2, 0, -3, -3, 0, -2, -3, 0, -3, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_null})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it) { RecastMeshBuilder builder(mSettings, mBounds); builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getWater(), std::vector({ RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} })); } 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(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, 1, })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({2, 1, 0, 2, 1, 3})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } } openmw-openmw-0.47.0/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp000066400000000000000000000064561413061077700304210ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; CollisionShape mBoxShape {nullptr, mBoxShapeImpl}; btCompoundShape mCompoundShapeImpl {true}; CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; 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.47.0/apps/openmw_test_suite/detournavigator/settingsutils.cpp000066400000000000000000000040541413061077700300050ustar00rootroot00000000000000#include "operators.hpp" #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorGetTilePositionTest : Test { Settings 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 { Settings 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.47.0/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp000066400000000000000000000423471413061077700325720ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { Settings mSettings; std::vector mChangedTiles; DetourNavigatorTileCachedRecastMeshManagerTest() { mSettings.mBorderSize = 16; mSettings.mCellSize = 0.2f; mSettings.mRecastScaleFactor = 0.017647058823529415f; mSettings.mTileSize = 64; } void onChangedTile(const TilePosition& tilePosition) { mChangedTiles.push_back(tilePosition); } }; TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) { TileCachedRecastMeshManager manager(mSettings); EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, has_tile_for_empty_should_return_false) { TileCachedRecastMeshManager manager(mSettings); EXPECT_FALSE(manager.hasTile(TilePosition(0, 0))); } 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(nullptr, boxShape); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); } 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_THAT( mChangedTiles, ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), TilePosition(1, -1), TilePosition(1, 0)) ); } 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_EQ(mChangedTiles, std::vector()); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) { 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_EQ(manager.getMesh(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); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); 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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (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(nullptr, boxShape); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); 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, btTransform::getIdentity())); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -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, btTransform::getIdentity())); 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); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_FALSE(manager.hasTile(TilePosition(x, y))); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); } } openmw-openmw-0.47.0/apps/openmw_test_suite/esm/000077500000000000000000000000001413061077700217245ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/esm/test_fixed_string.cpp000066400000000000000000000053061413061077700261600ustar00rootroot00000000000000#include #include "components/esm/esmcommon.hpp" TEST(EsmFixedString, operator__eq_ne) { { SCOPED_TRACE("asdc == asdc"); ESM::NAME name; name.assign("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"); ESM::NAME name; name.assign("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]"); ESM::NAME name; name.assign("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)"); ESM::NAME name; name.assign("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)"); ESM::NAME name; name.assign("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)"); ESM::NAME name; name.assign("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, struct_size) { ASSERT_EQ(4, sizeof(ESM::NAME)); ASSERT_EQ(32, sizeof(ESM::NAME32)); ASSERT_EQ(64, sizeof(ESM::NAME64)); } TEST(EsmFixedString, is_pod) { ASSERT_TRUE(std::is_pod::value); ASSERT_TRUE(std::is_pod::value); ASSERT_TRUE(std::is_pod::value); } openmw-openmw-0.47.0/apps/openmw_test_suite/esm/variant.cpp000066400000000000000000000377111413061077700241050ustar00rootroot00000000000000#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; ESM::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; ESM::ESMReader reader; reader.open(std::make_shared(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; ESM::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.47.0/apps/openmw_test_suite/misc/000077500000000000000000000000001413061077700220735ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/misc/test_endianness.cpp000066400000000000000000000060241413061077700257670ustar00rootroot00000000000000#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, 0x0000); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF); Misc::swapEndiannessInplace(n12); EXPECT_EQ(n12, 0x0201); Misc::swapEndiannessInplace(n12); EXPECT_EQ(n12, 0x0102); Misc::swapEndiannessInplace(fortytwo); EXPECT_EQ(fortytwo, 0x4200); Misc::swapEndiannessInplace(fortytwo); EXPECT_EQ(fortytwo, 0x0042); } 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, 0x00000000); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x00000000); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x04030201); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x01020304); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFFFFF); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFFFFF); } 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'0000); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000'0000'0000'0000); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x0807'0605'0403'0201); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x0102'0304'0506'0708); } 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.47.0/apps/openmw_test_suite/misc/test_stringops.cpp000066400000000000000000000033211413061077700256650ustar00rootroot00000000000000#include #include "components/misc/stringops.hpp" 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); } void TearDown() override { } bool matches(const std::string& keyword) { return Misc::StringUtils::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 ); } openmw-openmw-0.47.0/apps/openmw_test_suite/mwdialogue/000077500000000000000000000000001413061077700232755ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp000066400000000000000000000042451413061077700277170ustar00rootroot00000000000000#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" ASSERT_TRUE (matches.size() == 2); ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "foo bar"); ASSERT_TRUE (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); ASSERT_TRUE (matches.size() == 1); ASSERT_TRUE (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); ASSERT_TRUE (matches.size() == 1); ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); } openmw-openmw-0.47.0/apps/openmw_test_suite/mwworld/000077500000000000000000000000001413061077700226335ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/mwworld/test_store.cpp000066400000000000000000000237401413061077700255400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwmechanics/spelllist.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 std::vector readerList; readerList.resize(mContentFiles.size()); int index=0; for (const auto & mContentFile : mContentFiles) { ESM::ESMReader lEsm; lEsm.setEncoder(nullptr); lEsm.setIndex(index); lEsm.setGlobalReaderList(&readerList); lEsm.open(mContentFile.string()); readerList[index] = lEsm; mEsmStore.load(readerList[index], &dummyListener); ++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::EscapePathContainer(), "data")->multitoken()->composing()) ("content", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")); boost::program_options::notify(variables); mConfigurationManager.readConfiguration(variables, desc, true); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = Files::EscapePath::toPathContainer(variables["data"].as()); } Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) { dataLocal.push_back(local); } mConfigurationManager.processPaths (dataDirs); mConfigurationManager.processPaths (dataLocal, true); if (!dataLocal.empty()) dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); Files::Collections collections (dataDirs, true); std::vector contentFiles = variables["content"].as().toStdStringVector(); for (auto & contentFile : contentFiles) 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 = "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 = "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 Files::IStreamPtr getEsmFile(T record, bool deleted) { ESM::ESMWriter writer; auto* stream = new std::stringstream; writer.setFormat(0); writer.save(*stream); writer.startRecord(T::sRecordId); record.save(writer, deleted); writer.endRecord(T::sRecordId); return Files::IStreamPtr(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; std::vector readerList; readerList.push_back(reader); reader.setGlobalReaderList(&readerList); // master file inserts a record Files::IStreamPtr file = getEsmFile(record, false); reader.open(file, "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); // now a plugin deletes it file = getEsmFile(record, true); reader.open(file, "filename"); mEsmStore.load(reader, &dummyListener); 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 file = getEsmFile(record, false); reader.open(file, "filename"); mEsmStore.load(reader, &dummyListener); 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; std::vector readerList; readerList.push_back(reader); reader.setGlobalReaderList(&readerList); // master file inserts a record Files::IStreamPtr file = getEsmFile(record, false); reader.open(file, "filename"); mEsmStore.load(reader, &dummyListener); 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"; file = getEsmFile(record, false); reader.open(file, "filename"); mEsmStore.load(reader, &dummyListener); 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.47.0/apps/openmw_test_suite/nifloader/000077500000000000000000000000001413061077700231035ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp000066400000000000000000001222571413061077700276730ustar00rootroot00000000000000#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()); } } 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 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, rhs.mCollisionShape) && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) && lhs.mCollisionBox.extents == rhs.mCollisionBox.extents && lhs.mCollisionBox.center == rhs.mCollisionBox.center && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) { return stream << "Resource::BulletShape {" << value.mCollisionShape << ", " << value.mAvoidCollisionShape << ", " << "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", " << "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", " << value.mAnimatedShapes << "}"; } } 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.parent = 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::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(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::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) }; TestBulletNifLoader() { init(mNode); init(mNode2); init(mNiNode); init(mNiNode2); init(mNiNode3); init(mNiTriShape); init(mNiTriShape2); 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); } }; 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); } 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::NiNode::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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = 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 = shape.release(); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::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); 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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = 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 = 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::NiNode::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); 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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = 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 = 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::NiNode::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); 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); 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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = 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 = 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); mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::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); 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.extents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.center = 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 = 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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = 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 = 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.extents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { 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 = 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.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 = 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) { 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 = 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); 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 = 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 = 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.parent = &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 = 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; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; 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 = 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::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parent = &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 = 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::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parent = &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(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); 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 = shape.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) { 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 = 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); 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(); 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_starting_with_nc_should_return_shape_with_null_collision_shape) { mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); 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_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) { mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); 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_should_return_shape_with_null_collision_shape) { mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); 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); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; 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 = new Resource::TriangleMeshShape(triangles.release(), true); EXPECT_EQ(*result, expected); } } openmw-openmw-0.47.0/apps/openmw_test_suite/openmw_test_suite.cpp000066400000000000000000000005741413061077700254270ustar00rootroot00000000000000#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.47.0/apps/openmw_test_suite/settings/000077500000000000000000000000001413061077700230005ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/settings/parser.cpp000066400000000000000000000266761413061077700250210ustar00rootroot00000000000000#include #include #include namespace { using namespace testing; using namespace Settings; struct SettingsFileParserTest : Test { SettingsFileParser mLoader; SettingsFileParser mSaver; template void withSettingsFile( const std::string& content, F&& f) { const auto path = std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".cfg"; { boost::filesystem::ofstream stream; stream.open(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.47.0/apps/openmw_test_suite/shader/000077500000000000000000000000001413061077700224065ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/openmw_test_suite/shader/parsedefines.cpp000066400000000000000000000146221413061077700255670ustar00rootroot00000000000000#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.47.0/apps/openmw_test_suite/shader/parsefors.cpp000066400000000000000000000054651413061077700251300ustar00rootroot00000000000000#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"; }; 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.47.0/apps/openmw_test_suite/shader/shadermanager.cpp000066400000000000000000000173051413061077700257210ustar00rootroot00000000000000#include #include #include 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) { const auto path = UnitTest::GetInstance()->current_test_info()->name() + suffix + ".glsl"; { boost::filesystem::ofstream stream; stream.open(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.47.0/apps/wizard/000077500000000000000000000000001413061077700166635ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/wizard/CMakeLists.txt000066400000000000000000000063771413061077700214400ustar00rootroot00000000000000 set(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 ) # Headers that must be pre-processed set(WIZARD_HEADER_MOC componentselectionpage.hpp conclusionpage.hpp existinginstallationpage.hpp importpage.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_HEADER_MOC ${WIZARD_HEADER_MOC} 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_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) 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} ${MOC_SRCS} ${UI_HDRS} ) target_link_libraries(openmw-wizard components ) 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) openmw-openmw-0.47.0/apps/wizard/componentselectionpage.cpp000066400000000000000000000137741413061077700241500ustar00rootroot00000000000000#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.47.0/apps/wizard/componentselectionpage.hpp000066400000000000000000000011551413061077700241430ustar00rootroot00000000000000#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.47.0/apps/wizard/conclusionpage.cpp000066400000000000000000000037351413061077700224100ustar00rootroot00000000000000#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.47.0/apps/wizard/conclusionpage.hpp000066400000000000000000000007111413061077700224040ustar00rootroot00000000000000#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.47.0/apps/wizard/existinginstallationpage.cpp000066400000000000000000000122711413061077700245030ustar00rootroot00000000000000#include "existinginstallationpage.hpp" #include #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; } 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; } openmw-openmw-0.47.0/apps/wizard/existinginstallationpage.hpp000066400000000000000000000013131413061077700245030ustar00rootroot00000000000000#ifndef EXISTINGINSTALLATIONPAGE_HPP #define EXISTINGINSTALLATIONPAGE_HPP #include "ui_existinginstallationpage.h" 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; protected: void initializePage() override; }; } #endif // EXISTINGINSTALLATIONPAGE_HPP openmw-openmw-0.47.0/apps/wizard/importpage.cpp000066400000000000000000000007001413061077700215330ustar00rootroot00000000000000#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); } int Wizard::ImportPage::nextId() const { return MainWizard::Page_Conclusion; } openmw-openmw-0.47.0/apps/wizard/importpage.hpp000066400000000000000000000005651413061077700215510ustar00rootroot00000000000000#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.47.0/apps/wizard/inisettings.cpp000066400000000000000000000141411413061077700217300ustar00rootroot00000000000000#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(']')); 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.47.0/apps/wizard/inisettings.hpp000066400000000000000000000021721413061077700217360ustar00rootroot00000000000000#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.47.0/apps/wizard/installationpage.cpp000066400000000000000000000203151413061077700227260ustar00rootroot00000000000000#include "installationpage.hpp" #include #include #include #include #include #include "mainwizard.hpp" Wizard::InstallationPage::InstallationPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); mFinished = false; mThread = new QThread(); mUnshield = new UnshieldWorker(); mUnshield->moveToThread(mThread); connect(mThread, SIGNAL(started()), mUnshield, SLOT(extract())); connect(mUnshield, SIGNAL(finished()), mThread, SLOT(quit())); connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); connect(mUnshield, SIGNAL(error(QString, QString)), this, SLOT(installationError(QString, QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), installProgressLabel, SLOT(setText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), logTextEdit, SLOT(appendPlainText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), mWizard, SLOT(addLogText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); } Wizard::InstallationPage::~InstallationPage() { if (mThread->isRunning()) { mUnshield->stopWorker(); mThread->quit(); mThread->wait(); } delete mUnshield; delete mThread; } 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::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.47.0/apps/wizard/installationpage.hpp000066400000000000000000000017641413061077700227420ustar00rootroot00000000000000#ifndef INSTALLATIONPAGE_HPP #define INSTALLATIONPAGE_HPP #include #include "unshield/unshieldworker.hpp" #include "ui_installationpage.h" #include "inisettings.hpp" class QThread; namespace Wizard { class MainWizard; class IniSettings; class UnshieldWorker; class InstallationPage : public QWizardPage, private Ui::InstallationPage { Q_OBJECT public: InstallationPage(QWidget *parent); ~InstallationPage(); int nextId() const override; bool isComplete() const override; private: MainWizard *mWizard; bool mFinished; QThread* mThread; UnshieldWorker *mUnshield; void startInstallation(); private slots: void showFileDialog(Wizard::Component component); void installationFinished(); void installationError(const QString &text, const QString &details); protected: void initializePage() override; }; } #endif // INSTALLATIONPAGE_HPP openmw-openmw-0.47.0/apps/wizard/installationtargetpage.cpp000066400000000000000000000065571413061077700241510ustar00rootroot00000000000000#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.47.0/apps/wizard/installationtargetpage.hpp000066400000000000000000000013651413061077700241460ustar00rootroot00000000000000#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.47.0/apps/wizard/intropage.cpp000066400000000000000000000006041413061077700213570ustar00rootroot00000000000000#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.47.0/apps/wizard/intropage.hpp000066400000000000000000000005551413061077700213710ustar00rootroot00000000000000#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.47.0/apps/wizard/languageselectionpage.cpp000066400000000000000000000027411413061077700237210ustar00rootroot00000000000000#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.47.0/apps/wizard/languageselectionpage.hpp000066400000000000000000000007701413061077700237260ustar00rootroot00000000000000#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.47.0/apps/wizard/main.cpp000066400000000000000000000020311413061077700203070ustar00rootroot00000000000000#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.47.0/apps/wizard/mainwizard.cpp000066400000000000000000000354471413061077700215510ustar00rootroot00000000000000#include "mainwizard.hpp" #include #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.

"); 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())); msgBox.exec(); return qApp->quit(); } 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())); msgBox.exec(); return qApp->quit(); } 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())); msgBox.exec(); return qApp->quit(); } 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())); return qApp->quit(); } 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())); msgBox.exec(); return qApp->quit(); } 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")); } // 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)); #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)); msgBox.exec(); return qApp->quit(); } } // 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())); msgBox.exec(); return qApp->quit(); } 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())); msgBox.exec(); return qApp->quit(); } 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.47.0/apps/wizard/mainwizard.hpp000066400000000000000000000037341413061077700215500ustar00rootroot00000000000000#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(); 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.47.0/apps/wizard/methodselectionpage.cpp000066400000000000000000000017541413061077700234210ustar00rootroot00000000000000#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.47.0/apps/wizard/methodselectionpage.hpp000066400000000000000000000007601413061077700234220ustar00rootroot00000000000000#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.47.0/apps/wizard/unshield/000077500000000000000000000000001413061077700204765ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/wizard/unshield/unshieldworker.cpp000066400000000000000000000660161413061077700242600ustar00rootroot00000000000000#include "unshieldworker.hpp" #include #include #include #include #include #include #include #include #include #include #include Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : QObject(parent), 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; } 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) { 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 (!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; } openmw-openmw-0.47.0/apps/wizard/unshield/unshieldworker.hpp000066400000000000000000000066271413061077700242670ustar00rootroot00000000000000#ifndef UNSHIELDWORKER_HPP #define UNSHIELDWORKER_HPP #include #include #include #include #include #include #include #include "../inisettings.hpp" namespace Wizard { enum Component { Component_Morrowind, Component_Tribunal, Component_Bloodmoon }; class UnshieldWorker : public QObject { Q_OBJECT public: UnshieldWorker(QObject *parent = nullptr); ~UnshieldWorker(); void stopWorker(); void setInstallComponent(Wizard::Component component, bool install); void setDiskPath(const QString &path); void setPath(const QString &path); void setIniPath(const QString &path); QString getPath(); QString getIniPath(); void setIniCodec(QTextCodec *codec); bool setupSettings(); 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; 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 textChanged(const QString &text); void error(const QString &text, const QString &details); void progressChanged(int progress); }; } #endif // UNSHIELDWORKER_HPP openmw-openmw-0.47.0/apps/wizard/utils/000077500000000000000000000000001413061077700200235ustar00rootroot00000000000000openmw-openmw-0.47.0/apps/wizard/utils/componentlistwidget.cpp000066400000000000000000000021671413061077700246370ustar00rootroot00000000000000#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.47.0/apps/wizard/utils/componentlistwidget.hpp000066400000000000000000000011101413061077700246270ustar00rootroot00000000000000#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.47.0/appveyor.yml000066400000000000000000000054641413061077700170210ustar00rootroot00000000000000version: "{build}" branches: only: - master - /openmw-.*$/ - appveyor environment: matrix: - msvc: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - msvc: 2019 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 platform: # - Win32 - x64 configuration: # - Debug - Release # - RelWithDebInfo # For the Qt, Boost, CMake, etc installs #os: Visual Studio 2017 # We want the git revision for versioning, # so shallow clones don't work. clone_depth: 1 cache: - C:\projects\openmw\deps\Bullet-2.87-msvc2015-win32.7z - C:\projects\openmw\deps\Bullet-2.87-msvc2015-win64.7z - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win32.7z - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win64.7z - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win32.7z - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win64.7z - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win32.zip - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win64.zip - C:\projects\openmw\deps\ffmpeg-3.2.4-win32.zip - C:\projects\openmw\deps\ffmpeg-3.2.4-win64.zip - C:\projects\openmw\deps\OpenAL-Soft-1.19.1.zip - C:\projects\openmw\deps\SDL2-2.0.7.zip clone_folder: C:\projects\openmw install: - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% before_build: - cmd: git submodule update --init --recursive - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install build_script: - cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32 - cmd: if %PLATFORM%==x64 set build=MSVC%msvc%_64 - cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - cmd: cmake --install %build% --config %configuration% after_build: - cmd: if %PLATFORM%==x64 7z a OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit%.zip %APPVEYOR_BUILD_FOLDER%\install -xr"!*.pdb" #- cmd: if %PLATFORM%==x64 7z a OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit%_pdb.zip %APPVEYOR_BUILD_FOLDER%\MSVC%msvc%_64\%configuration%\*.pdb test: off #notifications: # - provider: Email # to: # - # on_build_failure: true # on_build_status_changed: true artifacts: - path: OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit%.zip name: OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit% #- path: OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit%_pdb.zip # name: OpenMW_MSVC%msvc%_%platform%_%appveyor_pull_request_number%_%appveyor_pull_request_head_commit%_pdb openmw-openmw-0.47.0/cmake/000077500000000000000000000000001413061077700155005ustar00rootroot00000000000000openmw-openmw-0.47.0/cmake/COPYING-CMAKE-SCRIPTS000066400000000000000000000027221413061077700205010ustar00rootroot00000000000000The 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.47.0/cmake/CheckBulletPrecision.cmake000066400000000000000000000012771413061077700225520ustar00rootroot00000000000000set(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.47.0/cmake/FindFFmpeg.cmake000066400000000000000000000105251413061077700204520ustar00rootroot00000000000000# 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.47.0/cmake/FindGMock.cmake000066400000000000000000000160601413061077700203060ustar00rootroot00000000000000# 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" IMPORTED_LOCATION "${GMOCK_LIBRARY}") 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" IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") 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.47.0/cmake/FindLIBUNSHIELD.cmake000066400000000000000000000020001413061077700210350ustar00rootroot00000000000000# 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.47.0/cmake/FindLZ4.cmake000066400000000000000000000100741413061077700177160ustar00rootroot00000000000000# 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) 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.47.0/cmake/FindMyGUI.cmake000066400000000000000000000031751413061077700202430ustar00rootroot00000000000000# - 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.47.0/cmake/FindOSGPlugins.cmake000066400000000000000000000051001413061077700212710ustar00rootroot00000000000000# 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.47.0/cmake/FindRecastNavigation.cmake000066400000000000000000000203541413061077700225500ustar00rootroot00000000000000# 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.47.0/cmake/FindSDL2.cmake000066400000000000000000000121601413061077700200070ustar00rootroot00000000000000# 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.47.0/cmake/FindSphinx.cmake000066400000000000000000000062761413061077700205670ustar00rootroot00000000000000# - 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.47.0/cmake/FindTinyXML.cmake000066400000000000000000000010421413061077700206040ustar00rootroot00000000000000# 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.47.0/cmake/GitVersion.cmake000066400000000000000000000020721413061077700205740ustar00rootroot00000000000000execute_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.47.0/cmake/LibFindMacros.cmake000066400000000000000000000337321413061077700211660ustar00rootroot00000000000000# 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.47.0/cmake/OpenMWMacros.cmake000066400000000000000000000171711413061077700210230ustar00rootroot00000000000000function(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_FILES "${f}") endforeach (f) file (GLOB MOC_H "${dir}/${u}.hpp") foreach (fi ${MOC_H}) list (APPEND COMPONENT_MOC_FILES "${fi}") endforeach (fi) 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_qt_unit project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_qt_unit) macro (add_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") endmacro (add_hdr) macro (add_qt_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") endmacro (add_qt_hdr) macro (opencs_units dir) foreach (u ${ARGN}) add_qt_unit (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_units) macro (opencs_units_noqt dir) foreach (u ${ARGN}) add_unit (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_units_noqt) macro (opencs_hdrs dir) foreach (u ${ARGN}) add_qt_hdr (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_hdrs) macro (opencs_hdrs_noqt dir) foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_hdrs_noqt) 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.47.0/cmake/WholeArchive.cmake000066400000000000000000000011071413061077700210610ustar00rootroot00000000000000function (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.47.0/cmake/base64.cmake000066400000000000000000000044141413061077700175710ustar00rootroot00000000000000# 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.47.0/components/000077500000000000000000000000001413061077700166055ustar00rootroot00000000000000openmw-openmw-0.47.0/components/CMakeLists.txt000066400000000000000000000213561413061077700213540ustar00rootroot00000000000000project (Components) # 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 (settings settings parser ) add_component_dir (bsa bsa_file compressedbsafile memorystream ) 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 ) 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 unrefqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller ) add_component_dir (nif controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream ) 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 defs esmcommon 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 records 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 util custommarkerstate stolenitems transport animationstate controlsstate mappings ) add_component_dir (esmterrain storage ) add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread ) 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 escape lowlevelfile constrainedfilestream memorystream ) 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 ) 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 ) add_component_dir (version version ) add_component_dir (fallback fallback validate ) if(WIN32) add_component_dir (crashcatcher windows_crashcatcher windows_crashmonitor windows_crashshm ) 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 ) 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_dir (misc helpviewer ) QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) 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}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) 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} LZ4::LZ4 RecastNavigation::DebugUtils RecastNavigation::Detour RecastNavigation::Recast Base64 ) target_link_libraries(components ${BULLET_LIBRARIES}) if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY} ${Boost_ZLIB_LIBRARY}) endif() if (USE_QT) target_link_libraries(components Qt5::Widgets Qt5::Core) endif() if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) 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) openmw-openmw-0.47.0/components/bsa/000077500000000000000000000000001413061077700173525ustar00rootroot00000000000000openmw-openmw-0.47.0/components/bsa/bsa_file.cpp000066400000000000000000000253561413061077700216350ustar00rootroot00000000000000/* 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 using namespace Bsa; /// Error handling 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; }); for (size_t i = 0; i < filenum; i++) { FileStruct& fs = mFiles[i]; // Add the file name to the lookup mLookup[fs.name()] = i; } 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()); } /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { auto it = mLookup.find(str); if(it == mLookup.end()) return -1; size_t res = it->second; assert(res < mFiles.size()); return static_cast(res); } /// 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(); mLookup.clear(); mIsLoaded = false; } Files::IStreamPtr BSAFile::getFile(const char *file) { assert(file); int i = getIndex(file); if(i == -1) fail("File not found: " + std::string(file)); const FileStruct &fs = mFiles[i]; return Files::openConstrainedFileStream (mFilename.c_str (), fs.offset, fs.fileSize); } Files::IStreamPtr BSAFile::getFile(const FileStruct *file) { return Files::openConstrainedFileStream (mFilename.c_str (), 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; mLookup[filename.c_str()] = mFiles.size() - 1; stream.seekp(0, std::ios::end); file.seekg(0, std::ios::beg); stream << file.rdbuf(); } openmw-openmw-0.47.0/components/bsa/bsa_file.hpp000066400000000000000000000103411413061077700216260ustar00rootroot00000000000000/* 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 #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; /// Case insensitive string comparison struct iltstr { bool operator()(const std::string& s1, const std::string& s2) const { return Misc::StringUtils::ciLess(s1, s2); } }; /** A map used for fast file name lookup. The value is the index into the files[] vector above. The iltstr ensures that file name checks are case insensitive. */ typedef std::map Lookup; Lookup mLookup; /// Error handling void fail(const std::string &msg); /// Read header information from the input source virtual void readHeader(); virtual void writeHeader(); /// Get the index of a given file name, or -1 if not found /// @note Thread safe. int getIndex(const char *str) const; 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 * ----------------------------------- */ /// Check if a file exists virtual bool exists(const char *file) const { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. * @note Thread safe. */ virtual Files::IStreamPtr getFile(const char *file); /** Open a file contained in the archive. * @note Thread safe. */ virtual Files::IStreamPtr getFile(const FileStruct* file); virtual 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.47.0/components/bsa/compressedbsafile.cpp000066400000000000000000000411301413061077700235470ustar00rootroot00000000000000/* 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 #include #if defined(_MSC_VER) #pragma warning (push) #pragma warning (disable : 4706) #include #pragma warning (pop) #else #include #endif #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); boost::scoped_array buf(new char[size]); filestream.read(buf.get(), size); if (buf[size - 1] != 0) { str.assign(buf.get(), size); if (str.size() != ((size_t)size)) { fail("getBZString string size mismatch"); } } else { str.assign(buf.get(), 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); mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; 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(); p.remove_filename(); std::string folder = p.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.c_str(), 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); } std::shared_ptr memoryStreamPtr = std::make_shared(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 { boost::scoped_array buffer(new char[size]); fileStream->read(buffer.get(), 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.get(), &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::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); } BsaVersion CompressedBSAFile::detectVersion(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.c_str(), 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.47.0/components/bsa/compressedbsafile.hpp000066400000000000000000000064051413061077700235620ustar00rootroot00000000000000/* 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 namespace Bsa { enum BsaVersion { BSAVER_UNKNOWN = 0x0, BSAVER_UNCOMPRESSED = 0x100, BSAVER_COMPRESSED = 0x415342 //B, S, A }; class CompressedBSAFile : public 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: CompressedBSAFile(); virtual ~CompressedBSAFile(); //checks version of BSA from file header static BsaVersion detectVersion(std::string filePath); /// Read header information from the input source void readHeader() override; Files::IStreamPtr getFile(const char* filePath) override; Files::IStreamPtr getFile(const FileStruct* fileStruct) override; void addFile(const std::string& filename, std::istream& file) override; }; } #endif openmw-openmw-0.47.0/components/bsa/memorystream.cpp000066400000000000000000000026511413061077700226060ustar00rootroot00000000000000/* 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.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 upgrade added by Azdul 2019 */ #include "memorystream.hpp" namespace Bsa { MemoryInputStreamBuf::MemoryInputStreamBuf(size_t bufferSize) : mBufferPtr(bufferSize) { this->setg(mBufferPtr.data(), mBufferPtr.data(), mBufferPtr.data() + bufferSize); } char* MemoryInputStreamBuf::getRawData() { return mBufferPtr.data(); } MemoryInputStream::MemoryInputStream(size_t bufferSize) : MemoryInputStreamBuf(bufferSize), std::istream(static_cast(this)) { } char* MemoryInputStream::getRawData() { return MemoryInputStreamBuf::getRawData(); } } openmw-openmw-0.47.0/components/bsa/memorystream.hpp000066400000000000000000000032601413061077700226100ustar00rootroot00000000000000/* 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 namespace Bsa { /** Class used internally by MemoryInputStream. */ class MemoryInputStreamBuf : public std::streambuf { public: explicit MemoryInputStreamBuf(size_t bufferSize); virtual char* getRawData(); private: //correct call to delete [] on C++ 11 std::vector mBufferPtr; }; /** 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 : virtual MemoryInputStreamBuf, std::istream { public: explicit MemoryInputStream(size_t bufferSize); char* getRawData() override; }; } #endif openmw-openmw-0.47.0/components/bullethelpers/000077500000000000000000000000001413061077700214575ustar00rootroot00000000000000openmw-openmw-0.47.0/components/bullethelpers/aabb.hpp000066400000000000000000000013461413061077700230610ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_AABB_H #define OPENMW_COMPONENTS_BULLETHELPERS_AABB_H #include #include #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.47.0/components/bullethelpers/operators.hpp000066400000000000000000000065221413061077700242130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H #define OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H #include #include #include #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) << ")"; } } 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.47.0/components/bullethelpers/processtrianglecallback.hpp000066400000000000000000000016231413061077700270530ustar00rootroot00000000000000#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.47.0/components/bullethelpers/transformboundingbox.hpp000066400000000000000000000026101413061077700264410ustar00rootroot00000000000000#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.47.0/components/compiler/000077500000000000000000000000001413061077700204175ustar00rootroot00000000000000openmw-openmw-0.47.0/components/compiler/context.hpp000066400000000000000000000027541413061077700226240ustar00rootroot00000000000000#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? virtual bool isJournalId (const std::string& name) const = 0; ///< Does \a name match a journal ID? }; } #endif openmw-openmw-0.47.0/components/compiler/controlparser.cpp000066400000000000000000000213331413061077700240220ustar00rootroot00000000000000#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.47.0/components/compiler/controlparser.hpp000066400000000000000000000043051413061077700240270ustar00rootroot00000000000000#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.47.0/components/compiler/declarationparser.cpp000066400000000000000000000051571413061077700246350ustar00rootroot00000000000000#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); } void Compiler::DeclarationParser::reset() { mState = State_Begin; } openmw-openmw-0.47.0/components/compiler/declarationparser.hpp000066400000000000000000000021521413061077700246320ustar00rootroot00000000000000#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? void reset() override; }; } #endif openmw-openmw-0.47.0/components/compiler/discardparser.cpp000066400000000000000000000041571413061077700237600ustar00rootroot00000000000000#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==CommaState || 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==CommaState || 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 || mState==CommaState) { 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_comma && mState==StartState) { if (isEmpty()) mTokenLoc = loc; start(); mState = CommaState; return true; } if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) { 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.47.0/components/compiler/discardparser.hpp000066400000000000000000000030371413061077700237610ustar00rootroot00000000000000#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, CommaState, 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.47.0/components/compiler/errorhandler.cpp000066400000000000000000000040601413061077700236120ustar00rootroot00000000000000#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.47.0/components/compiler/errorhandler.hpp000066400000000000000000000043051413061077700236210ustar00rootroot00000000000000#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.47.0/components/compiler/exception.hpp000066400000000000000000000015611413061077700231310ustar00rootroot00000000000000#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.47.0/components/compiler/exprparser.cpp000066400000000000000000000512441413061077700233240ustar00rootroot00000000000000#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 { // no comma was used between arguments 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 { // no comma was used between arguments 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 { // no comma was used between arguments scanner.putbackName (name, loc); return false; } } bool ExprParser::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) { 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) { if (keyword==Scanner::K_getsquareroot) { start(); mTokenLoc = loc; parseArguments ("f", scanner); Generator::squareRoot (mCode); mOperands.push_back ('f'); mNextOperand = false; return true; } else { // 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 { // no comma was used between arguments 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); } if (code==Scanner::S_comma) { mTokenLoc = loc; if (mFirst) { // leading comma mFirst = false; return true; } // end marker scanner.putbackSpecial (code, loc); return false; } 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 { // no comma was used between arguments 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 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.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); 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.47.0/components/compiler/exprparser.hpp000066400000000000000000000067371413061077700233400ustar00rootroot00000000000000#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); ///< 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.47.0/components/compiler/extensions.cpp000066400000000000000000000147051413061077700233310ustar00rootroot00000000000000#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.47.0/components/compiler/extensions.hpp000066400000000000000000000111301413061077700233230ustar00rootroot00000000000000#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.47.0/components/compiler/extensions0.cpp000066400000000000000000001070301413061077700234030ustar00rootroot00000000000000#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; } } else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState)) { // ignoring comma (for now) return true; } 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.47.0/components/compiler/fileparser.hpp000066400000000000000000000034321413061077700232660ustar00rootroot00000000000000#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.47.0/components/compiler/generator.cpp000066400000000000000000000462741413061077700231260ustar00rootroot00000000000000#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 opSquareRoot (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (19)); } 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 squareRoot (CodeContainer& code) { opSquareRoot (code); } 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.47.0/components/compiler/generator.hpp000066400000000000000000000072741413061077700231300ustar00rootroot00000000000000#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 squareRoot (CodeContainer& code); 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.47.0/components/compiler/junkparser.cpp000066400000000000000000000022641413061077700233130ustar00rootroot00000000000000#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.47.0/components/compiler/junkparser.hpp000066400000000000000000000024451413061077700233210ustar00rootroot00000000000000#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.47.0/components/compiler/lineparser.cpp000066400000000000000000000407201413061077700232720ustar00rootroot00000000000000#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" #include "exception.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; } 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 || mState==MessageCommaState) { GetArgumentsFromMessageFormat processor; processor.process(name); std::string arguments = processor.getArguments(); if (!arguments.empty()) { mExprParser.reset(); mExprParser.parseArguments (arguments, scanner, mCode); } mName = name; mButtons = 0; mState = MessageButtonState; return true; } if (mState==MessageButtonState || mState==MessageButtonCommaState) { 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 || mState==MessageCommaState) { 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(); } try { // workaround for broken positioncell instructions. /// \todo add option to disable this std::unique_ptr errorDowngrade (nullptr); if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") errorDowngrade = std::make_unique (getErrorHandler()); 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); } catch (const SourceException&) { // Ignore argument exceptions for positioncell. /// \todo add option to disable this if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") { SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; } throw; } 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_comma && mState==MessageState) { mState = MessageCommaState; return true; } 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_comma && mState==MessageButtonState) { mState = MessageButtonCommaState; return true; } 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.47.0/components/compiler/lineparser.hpp000066400000000000000000000064221413061077700233000ustar00rootroot00000000000000#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, MessageCommaState, MessageButtonState, MessageButtonCommaState, 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(const std::string& message) override { mArguments.clear(); ::Misc::MessageFormatParser::process(message); } std::string getArguments() const { return mArguments; } }; } #endif openmw-openmw-0.47.0/components/compiler/literals.cpp000066400000000000000000000044521413061077700227470ustar00rootroot00000000000000#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.47.0/components/compiler/literals.hpp000066400000000000000000000025701413061077700227530ustar00rootroot00000000000000#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.47.0/components/compiler/locals.cpp000066400000000000000000000050611413061077700224020ustar00rootroot00000000000000#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, const std::string& 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, const std::string& 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 (const std::string& name) const { if (search ('s', name)) return 's'; if (search ('l', name)) return 'l'; if (search ('f', name)) return 'f'; return ' '; } int Locals::getIndex (const std::string& 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, const std::string& name) { get (type).push_back (Misc::StringUtils::lowerCase (name)); } void Locals::clear() { get ('s').clear(); get ('l').clear(); get ('f').clear(); } } openmw-openmw-0.47.0/components/compiler/locals.hpp000066400000000000000000000024401413061077700224050ustar00rootroot00000000000000#ifndef COMPILER_LOCALS_H_INCLUDED #define COMPILER_LOCALS_H_INCLUDED #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 (const std::string& name) const; ///< 's': short, 'l': long, 'f': float, ' ': does not exist. int getIndex (const std::string& name) const; ///< return index for local variable \a name (-1: does not exist). bool search (char type, const std::string& name) const; /// Return index for local variable \a name of type \a type (-1: variable does not /// exit). int searchIndex (char type, const std::string& name) const; const std::vector& get (char type) const; void write (std::ostream& localFile) const; ///< write declarations to file. void declare (char type, const std::string& name); ///< declares a variable. void clear(); ///< remove all declarations. }; } #endif openmw-openmw-0.47.0/components/compiler/nullerrorhandler.cpp000066400000000000000000000003351413061077700245060ustar00rootroot00000000000000#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.47.0/components/compiler/nullerrorhandler.hpp000066400000000000000000000010371413061077700245130ustar00rootroot00000000000000#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.47.0/components/compiler/opcodes.cpp000066400000000000000000000004241413061077700225570ustar00rootroot00000000000000#include "opcodes.hpp" namespace Compiler::Control { const char *controls[numberOfControls] = { "playercontrols", "playerfighting", "playerjumping", "playerlooking", "playermagic", "playerviewswitch", "vanitymode" }; } openmw-openmw-0.47.0/components/compiler/opcodes.hpp000066400000000000000000000572251413061077700225770ustar00rootroot00000000000000#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; } 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.47.0/components/compiler/output.cpp000066400000000000000000000032141413061077700224630ustar00rootroot00000000000000#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.47.0/components/compiler/output.hpp000066400000000000000000000016741413061077700225000ustar00rootroot00000000000000#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.47.0/components/compiler/parser.cpp000066400000000000000000000076201413061077700224240ustar00rootroot00000000000000#include "parser.hpp" #include "errorhandler.hpp" #include "exception.hpp" #include "scanner.hpp" #include namespace Compiler { // Report the error and throw an exception. 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. 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.47.0/components/compiler/parser.hpp000066400000000000000000000067141413061077700224340ustar00rootroot00000000000000#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: 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. 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.47.0/components/compiler/quickfileparser.cpp000066400000000000000000000024551413061077700243220ustar00rootroot00000000000000#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.47.0/components/compiler/quickfileparser.hpp000066400000000000000000000022121413061077700243160ustar00rootroot00000000000000#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.47.0/components/compiler/scanner.cpp000066400000000000000000000424231413061077700225610ustar00rootroot00000000000000#include "scanner.hpp" #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; 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; if (scanInt (c, parser, cont)) { 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); bool error = false; while (get (c)) { if (c.isDigit()) { c.appendTo(value); } else if (!c.isMinusSign() && isStringCharacter (c)) { error = true; c.appendTo(value); } else if (c=='.') { if (error) { putback (c); break; } return scanFloat (value, parser, cont); } else { putback (c); break; } } if (error) { /// workaround that allows names to begin with digits /// \todo disable TokenLoc loc (mLoc); mLoc.mLiteral.clear(); cont = parser.parseName (value, loc, *this); return true; // return false; } 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", "getsquareroot", 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) { 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=='.') { // check, if this starts a float literal if (get (c)) { putback (c); if (c.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()) { if (get (c)) { if (c=='>') special = S_ref; else { putback (c); 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_comma; 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 = ""; 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) { } void Scanner::scan (Parser& parser) { while (scanToken (parser)); } 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; } } openmw-openmw-0.47.0/components/compiler/scanner.hpp000066400000000000000000000170201413061077700225610ustar00rootroot00000000000000#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[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; 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, K_getsquareroot }; 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_comma, 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); /// \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(); }; } #endif openmw-openmw-0.47.0/components/compiler/scriptparser.cpp000066400000000000000000000052021413061077700236430ustar00rootroot00000000000000#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.47.0/components/compiler/scriptparser.hpp000066400000000000000000000030461413061077700236540ustar00rootroot00000000000000#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.47.0/components/compiler/skipparser.cpp000066400000000000000000000015531413061077700233120ustar00rootroot00000000000000#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.47.0/components/compiler/skipparser.hpp000066400000000000000000000024561413061077700233220ustar00rootroot00000000000000#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.47.0/components/compiler/streamerrorhandler.cpp000066400000000000000000000034501413061077700250300ustar00rootroot00000000000000#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.47.0/components/compiler/streamerrorhandler.hpp000066400000000000000000000025711413061077700250400ustar00rootroot00000000000000#ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED #include #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.47.0/components/compiler/stringparser.cpp000066400000000000000000000064541413061077700236570ustar00rootroot00000000000000#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), mState (StartState), mSmashCase (false), mDiscard (false) { } bool StringParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==StartState || mState==CommaState) { start(); mTokenLoc = loc; if (!mDiscard) { if (mSmashCase) Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); else Generator::pushString (mCode, mLiterals, name); } return false; } return Parser::parseName (name, loc, scanner); } 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 || keyword==Scanner::K_getsquareroot) { return parseName (loc.mLiteral, loc, scanner); } return Parser::parseKeyword (keyword, loc, scanner); } bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_comma && mState==StartState) { mState = CommaState; return true; } return Parser::parseSpecial (code, loc, scanner); } void StringParser::append (std::vector& code) { std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); } void StringParser::reset() { mState = StartState; 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.47.0/components/compiler/stringparser.hpp000066400000000000000000000036421413061077700236600ustar00rootroot00000000000000#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 { enum State { StartState, CommaState }; Literals& mLiterals; State mState; 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 parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character 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.47.0/components/compiler/tokenloc.hpp000066400000000000000000000005551413061077700227530ustar00rootroot00000000000000#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.47.0/components/config/000077500000000000000000000000001413061077700200525ustar00rootroot00000000000000openmw-openmw-0.47.0/components/config/gamesettings.cpp000066400000000000000000000377061413061077700232650ustar00rootroot00000000000000#include "gamesettings.hpp" #include "launchersettings.hpp" #include #include #include #include const char Config::GameSettings::sContentKey[] = "content"; 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); 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); if (!dataDirs.empty()) { QString path = QString::fromUtf8(dataDirs.front().string().c_str()); QDir dir(path); if (dir.exists()) mDataLocal = path; } } 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) { return readFile(stream, mSettings); } bool Config::GameSettings::readUserFile(QTextStream &stream) { return readFile(stream, mUserSettings); } bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap &settings) { 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; } } } 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& fileNames) { remove(sContentKey); for (const QString& fileName : fileNames) { setMultiValue(sContentKey, fileName); } } 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.47.0/components/config/gamesettings.hpp000066400000000000000000000051341413061077700232600ustar00rootroot00000000000000#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); } inline QStringList getDataDirs() const { return mDataDirs; } 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 readFile(QTextStream &stream, QMultiMap &settings); bool readUserFile(QTextStream &stream); bool writeFile(QTextStream &stream); bool writeFileWithComments(QFile &file); void setContentList(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 sContentKey[]; static bool isOrderedLine(const QString& line) ; }; } #endif // GAMESETTINGS_HPP openmw-openmw-0.47.0/components/config/launchersettings.cpp000066400000000000000000000124201413061077700241370ustar00rootroot00000000000000#include "launchersettings.hpp" #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::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::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) const QStringList files(gameSettings.getContentList()); // if openmw.cfg has no content, exit so we don't create an empty content list. if (files.isEmpty()) { return; } // 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))) { setCurrentContentListName(listName); return; } } // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); setContentList(newContentListName, files); } void Config::LauncherSettings::removeContentList(const QString &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& fileNames) { removeContentList(contentListName); QString key = makeContentListKey(contentListName); for (const QString& fileName : fileNames) { setMultiValue(key, fileName); } } QString Config::LauncherSettings::getCurrentContentListName() const { return value(QString(sCurrentContentListKey)); } 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.47.0/components/config/launchersettings.hpp000066400000000000000000000036151413061077700241520ustar00rootroot00000000000000#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& fileNames); void removeContentList(const QString &contentListName); void setCurrentContentListName(const QString &contentListName); QString getCurrentContentListName() 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 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 sContentListSuffix[]; }; } #endif // LAUNCHERSETTINGS_HPP openmw-openmw-0.47.0/components/config/settingsbase.hpp000066400000000000000000000060031413061077700232550ustar00rootroot00000000000000#ifndef SETTINGSBASE_HPP #define SETTINGSBASE_HPP #include #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) { QStringList values = mSettings.values(key); if (!values.contains(value)) mSettings.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); } 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.47.0/components/contentselector/000077500000000000000000000000001413061077700220205ustar00rootroot00000000000000openmw-openmw-0.47.0/components/contentselector/model/000077500000000000000000000000001413061077700231205ustar00rootroot00000000000000openmw-openmw-0.47.0/components/contentselector/model/contentmodel.cpp000066400000000000000000000477161413061077700263360ustar00rootroot00000000000000#include "contentmodel.hpp" #include "esmfile.hpp" #include #include #include #include #include ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) : QAbstractTableModel(parent), mWarningIcon(warningIcon), 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 = (file->gameFiles().count() == 0); 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. bool depFound = (dependencyIter.peekNext()->fileName().compare(fileName, Qt::CaseInsensitive) == 0); if (!depFound) continue; if (!gamefileChecked) { if (isChecked (dependencyIter.peekNext()->filePath())) gamefileChecked = (dependencyIter.peekNext()->isGameFile()); } // force it to iterate all files in cases where the current // dependency is a game file to ensure that a later duplicate // game file is / is not checked. // (i.e., break only if it's not a gamefile or the game file has been checked previously) if (gamefileChecked || !(dependencyIter.peekNext()->isGameFile())) break; } } if (gamefileChecked) { 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::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) { QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); for (const QString &path2 : dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path2)); if (item(info.fileName())) 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); } catch(std::runtime_error &e) { // An error occurred while reading the .esp qWarning() << "Error reading addon file: " << e.what(); continue; } } sortFiles(); } 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() { //first, sort the model such that all dependencies are ordered upstream (gamefile) first. bool movedFiles = true; int fileCount = mFiles.size(); //Dependency sort //iterate until no sorting of files occurs while (movedFiles) { movedFiles = false; //iterate each file, obtaining a reference to it's gamefiles list for (int i = 0; i < fileCount; i++) { QModelIndex idx1 = index (i, 0, QModelIndex()); const QStringList &gamefiles = mFiles.at(i)->gameFiles(); //iterate each file after the current file, verifying that none of it's //dependencies appear. for (int j = i + 1; j < fileCount; j++) { if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive) || (!mFiles.at(i)->isGameFile() && gamefiles.isEmpty() && mFiles.at(j)->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files { mFiles.move(j, i); QModelIndex idx2 = index (j, 0, QModelIndex()); emit dataChanged (idx1, idx2); movedFiles = true; } } if (movedFiles) break; } } } bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const { if (mCheckStates.contains(filepath)) return (mCheckStates[filepath] == Qt::Checked); return false; } bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const { return (flags(index) & Qt::ItemIsEnabled); } 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.47.0/components/contentselector/model/contentmodel.hpp000066400000000000000000000061171413061077700263310ustar00rootroot00000000000000#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); ~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); 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 (QModelIndex index) const; bool isChecked(const QString &filepath) const; bool setCheckState(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); void sortFiles(); /// 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; QHash mCheckStates; QSet mPluginsWithLoadOrderError; QString mEncoding; QIcon mWarningIcon; public: QString mMimeType; QStringList mMimeTypes; int mColumnCount; Qt::DropActions mDropActions; }; } #endif // CONTENTMODEL_HPP openmw-openmw-0.47.0/components/contentselector/model/esmfile.cpp000066400000000000000000000066051413061077700252570ustar00rootroot00000000000000#include "esmfile.hpp" #include #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.47.0/components/contentselector/model/esmfile.hpp000066400000000000000000000055641413061077700252670ustar00rootroot00000000000000#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.47.0/components/contentselector/model/loadordererror.cpp000066400000000000000000000006701413061077700266540ustar00rootroot00000000000000#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.47.0/components/contentselector/model/loadordererror.hpp000066400000000000000000000017561413061077700266670ustar00rootroot00000000000000#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.47.0/components/contentselector/model/modelitem.cpp000066400000000000000000000026121413061077700256040ustar00rootroot00000000000000#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.47.0/components/contentselector/model/modelitem.hpp000066400000000000000000000014301413061077700256060ustar00rootroot00000000000000#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.47.0/components/contentselector/model/naturalsort.cpp000066400000000000000000000066751413061077700262200ustar00rootroot00000000000000/* * 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.47.0/components/contentselector/model/naturalsort.hpp000066400000000000000000000006031413061077700262060ustar00rootroot00000000000000#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.47.0/components/contentselector/view/000077500000000000000000000000001413061077700227725ustar00rootroot00000000000000openmw-openmw-0.47.0/components/contentselector/view/combobox.cpp000066400000000000000000000020661413061077700253120ustar00rootroot00000000000000#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.47.0/components/contentselector/view/combobox.hpp000066400000000000000000000010221413061077700253060ustar00rootroot00000000000000#ifndef COMBOBOX_HPP #define COMBOBOX_HPP #include #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.47.0/components/contentselector/view/contentselector.cpp000066400000000000000000000215501413061077700267140ustar00rootroot00000000000000#include "contentselector.hpp" #include #include #include #include #include #include ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) { ui.setupUi(parent); ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); buildContentModel(); buildGameFileView(); buildAddonView(); } void ContentSelectorView::ContentSelector::buildContentModel() { QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon); } void ContentSelectorView::ContentSelector::buildGameFileView() { ui.gameFileView->setVisible (true); ui.gameFileView->setPlaceholderText(QString("Select a game file...")); connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); ui.gameFileView->setCurrentIndex(-1); 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 = -1; 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) { mContentModel->addFiles(path); // 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() != -1) ui.gameFileView->setCurrentIndex(-1); mContentModel->uncheckAll(); } void ContentSelectorView::ContentSelector::clearFiles() { mContentModel->clearFiles(); } QString ContentSelectorView::ContentSelector::currentFile() const { QModelIndex currentIdx = ui.addonView->currentIndex(); if (!currentIdx.isValid()) 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.47.0/components/contentselector/view/contentselector.hpp000066400000000000000000000044231413061077700267210ustar00rootroot00000000000000#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); QString currentFile() const; void addFiles(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() != -1; } QWidget *uiWidget() const { return ui.contentGroupBox; } QToolButton *refreshButton() const { return ui.refreshButton; } QLineEdit *searchFilter() const { return ui.searchFilter; } private: Ui::ContentSelector ui; void buildContentModel(); 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.47.0/components/crashcatcher/000077500000000000000000000000001413061077700212375ustar00rootroot00000000000000openmw-openmw-0.47.0/components/crashcatcher/crashcatcher.cpp000066400000000000000000000367531413061077700244130ustar00rootroot00000000000000#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 char altstack[SIGSTKSZ]; 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 */ altss.ss_sp = altstack; altss.ss_flags = 0; altss.ss_size = sizeof(altstack); 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 (const auto env = std::getenv("OPENMW_DISABLE_CRASH_CATCHER")) if (std::atol(env) != 0) return; 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.47.0/components/crashcatcher/crashcatcher.hpp000066400000000000000000000010421413061077700243770ustar00rootroot00000000000000#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.47.0/components/crashcatcher/windows_crashcatcher.cpp000066400000000000000000000145051413061077700261540ustar00rootroot00000000000000#include #include #include #include #include #include "windows_crashcatcher.hpp" #include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" #include namespace Crash { 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& crashLogPath) { 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(crashLogPath); 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::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& crashLogPath) { 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); memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); size_t length = crashLogPath.length(); if (length >= MAX_LONG_PATH) length = MAX_LONG_PATH - 1; strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length); mShm->mStartup.mLogFilePath[length] = '\0'; // 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.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"); 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 '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } } // namespace Crash openmw-openmw-0.47.0/components/crashcatcher/windows_crashcatcher.hpp000066400000000000000000000044371413061077700261640ustar00rootroot00000000000000#ifndef WINDOWS_CRASHCATCHER_HPP #define WINDOWS_CRASHCATCHER_HPP #include #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #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: CrashCatcher(int argc, char **argv, const std::string& crashLogPath); ~CrashCatcher(); 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& crashLogPath); 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.47.0/components/crashcatcher/windows_crashmonitor.cpp000066400000000000000000000131571413061077700262340ustar00rootroot00000000000000#undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include "windows_crashcatcher.hpp" #include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" #include namespace Crash { 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; 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; } void CrashMonitor::run() { try { // app waits for monitor start up, let it continue signalApp(); bool running = true; while (isAppAlive() && running) { if (waitApp()) { shmLock(); switch (mShm->mEvent) { case CrashSHM::Event::None: break; case CrashSHM::Event::Crashed: handleCrash(); running = false; break; case CrashSHM::Event::Shutdown: running = false; break; case CrashSHM::Event::Startup: break; } shmUnlock(); } } } catch (...) { Log(Debug::Error) << "Exception in crash monitor, exiting"; } signalApp(); } 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() { 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(mShm->mStartup.mLogFilePath); 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"; } } } // namespace Crash openmw-openmw-0.47.0/components/crashcatcher/windows_crashmonitor.hpp000066400000000000000000000015411413061077700262330ustar00rootroot00000000000000#ifndef WINDOWS_CRASHMONITOR_HPP #define WINDOWS_CRASHMONITOR_HPP #include namespace Crash { struct CrashSHM; class CrashMonitor final { public: CrashMonitor(HANDLE shmHandle); ~CrashMonitor(); void run(); private: HANDLE mAppProcessHandle = 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; void signalApp() const; bool waitApp() const; bool isAppAlive() const; void shmLock(); void shmUnlock(); void handleCrash(); }; } // namespace Crash #endif // WINDOWS_CRASHMONITOR_HPP openmw-openmw-0.47.0/components/crashcatcher/windows_crashshm.hpp000066400000000000000000000016611413061077700253360ustar00rootroot00000000000000#ifndef WINDOWS_CRASHSHM_HPP #define WINDOWS_CRASHSHM_HPP #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #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; struct CrashSHM { enum class Event { None, Startup, Crashed, Shutdown }; Event mEvent; struct Startup { HANDLE mAppProcessHandle; HANDLE mSignalApp; HANDLE mSignalMonitor; HANDLE mShmMutex; char mLogFilePath[MAX_LONG_PATH]; } mStartup; struct Crashed { DWORD mThreadId; CONTEXT mContext; EXCEPTION_RECORD mExceptionRecord; } mCrashed; }; } // namespace Crash #endif // WINDOWS_CRASHSHM_HPP openmw-openmw-0.47.0/components/debug/000077500000000000000000000000001413061077700176735ustar00rootroot00000000000000openmw-openmw-0.47.0/components/debug/debugging.cpp000066400000000000000000000153141413061077700223360ustar00rootroot00000000000000#include "debugging.hpp" #include #include #include #include #ifdef _WIN32 # include # undef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # include #endif 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 std::streamsize DebugOutputBase::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] = '['; uint64_t ms = std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count(); std::time_t t = ms / 1000; prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", std::localtime(&t)) + 1; char levelLetter = " EWIVD*"[int(level)]; 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); msg = msg.substr(lineSize); } return size; } Level DebugOutputBase::getLevelMarker(const char *str) { if (unsigned(*str) <= unsigned(Marker)) { return Level(*str); } return NoLevel; } void DebugOutputBase::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; } } static std::unique_ptr rawStdout = nullptr; std::ostream& getRawStdout() { return rawStdout ? *rawStdout : std::cout; } int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) { #if defined _WIN32 (void)Debug::attachParentConsole(); #endif rawStdout = std::make_unique(std::cout.rdbuf()); // Some objects used to redirect cout and cerr // Scope must be here, so this still works inside the catch block for logging exceptions std::streambuf* cout_rdbuf = std::cout.rdbuf (); std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); #if defined(_WIN32) && defined(_DEBUG) boost::iostreams::stream_buffer sb; #else boost::iostreams::stream_buffer coutsb; boost::iostreams::stream_buffer cerrsb; std::ostream oldcout(cout_rdbuf); std::ostream oldcerr(cerr_rdbuf); #endif const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; boost::filesystem::ofstream logfile; int ret = 0; try { Files::ConfigurationManager cfgMgr; #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 // Redirect cout and cerr to the log file // If we are collecting a stack trace, append to existing log file std::ios_base::openmode mode = std::ios::out; if(argc == 2 && strcmp(argv[1], crash_switch) == 0) mode |= std::ios::app; logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName), mode); coutsb.open (Debug::Tee(logfile, oldcout)); cerrsb.open (Debug::Tee(logfile, oldcerr)); std::cout.rdbuf (&coutsb); std::cerr.rdbuf (&cerrsb); #endif #if defined(_WIN32) const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string()); #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, (cfgMgr.getLogPath() / crashLogName).string()); #endif 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, (appName + ": Fatal error").c_str(), e.what(), nullptr); Log(Debug::Error) << "Error: " << e.what(); ret = 1; } // Restore cout and cerr std::cout.rdbuf(cout_rdbuf); std::cerr.rdbuf(cerr_rdbuf); Debug::CurrentDebugLevel = Debug::NoLevel; return ret; } openmw-openmw-0.47.0/components/debug/debugging.hpp000066400000000000000000000066001413061077700223410ustar00rootroot00000000000000#ifndef DEBUG_DEBUGGING_H #define DEBUG_DEBUGGING_H #include #include #include #include #include "debuglog.hpp" #if defined _WIN32 && defined _DEBUG # undef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # include #endif namespace Debug { // ANSI colors for terminal enum Color { Reset = 0, DarkGray = 90, Red = 91, Yellow = 93 }; class DebugOutputBase : public boost::iostreams::sink { public: DebugOutputBase() { if (CurrentDebugLevel == NoLevel) fillCurrentDebugLevel(); } virtual std::streamsize write(const char *str, std::streamsize size); virtual ~DebugOutputBase() = default; protected: static Level getLevelMarker(const char *str); static void fillCurrentDebugLevel(); virtual std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) { return size; } }; #ifdef _WIN32 bool attachParentConsole(); #endif #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() {} }; #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() {} 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 } // Can be used to print messages without timestamps std::ostream& getRawStdout(); int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); #endif openmw-openmw-0.47.0/components/debug/debuglog.cpp000066400000000000000000000001631413061077700221670ustar00rootroot00000000000000#include "debuglog.hpp" namespace Debug { Level CurrentDebugLevel = Level::NoLevel; } std::mutex Log::sLock; openmw-openmw-0.47.0/components/debug/debuglog.hpp000066400000000000000000000025161413061077700222000ustar00rootroot00000000000000#ifndef DEBUG_LOG_H #define DEBUG_LOG_H #include #include #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 { static std::mutex sLock; std::unique_lock mLock; public: // Locks a global lock while the object is alive Log(Debug::Level level) : mLock(sLock), mLevel(level) { // 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; if (mLevel <= Debug::CurrentDebugLevel) std::cout << static_cast(mLevel); } // Perfect forwarding wrappers to give the chain of objects to cout template Log& operator<<(T&& rhs) { if (mLevel <= Debug::CurrentDebugLevel) std::cout << std::forward(rhs); return *this; } ~Log() { if (mLevel <= Debug::CurrentDebugLevel) std::cout << std::endl; } private: Debug::Level mLevel; }; #endif openmw-openmw-0.47.0/components/debug/gldebug.cpp000066400000000000000000000232051413061077700220120ustar00rootroot00000000000000// 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 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"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeStr = "UNDEFINED_BEHAVIOR"; break; case GL_DEBUG_TYPE_PORTABILITY: typeStr = "PORTABILITY"; break; case GL_DEBUG_TYPE_PERFORMANCE: typeStr = "PERFORMANCE"; break; case GL_DEBUG_TYPE_OTHER: typeStr = "OTHER"; 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.47.0/components/debug/gldebug.hpp000066400000000000000000000051111413061077700220130ustar00rootroot00000000000000#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.47.0/components/detournavigator/000077500000000000000000000000001413061077700220225ustar00rootroot00000000000000openmw-openmw-0.47.0/components/detournavigator/areatype.hpp000066400000000000000000000010131413061077700243400ustar00rootroot00000000000000#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.47.0/components/detournavigator/asyncnavmeshupdater.cpp000066400000000000000000000465621413061077700266270ustar00rootroot00000000000000#include "asyncnavmeshupdater.hpp" #include "debug.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include "version.hpp" #include #include #include #include #include #include #include namespace { using DetourNavigator::ChangeType; using DetourNavigator::TilePosition; 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::map>& tilesPerHalfExtents, const std::set>& presentTiles) { int result = maxDistance; for (const auto& [halfExtents, tiles] : tilesPerHalfExtents) for (const TilePosition& tile : tiles) if (presentTiles.find(std::make_tuple(halfExtents, tile)) == presentTiles.end()) result = std::min(result, getManhattanDistance(position, tile)); return result; } } namespace DetourNavigator { static 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) << ")"; } AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager) : mSettings(settings) , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); } AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { mShouldStop = true; std::unique_lock lock(mMutex); mJobs = decltype(mJobs)(); mHasJob.notify_all(); lock.unlock(); for (auto& thread : mThreads) thread.join(); } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles) { bool playerTileChanged = false; { auto locked = mPlayerTile.lock(); playerTileChanged = *locked != playerTile; *locked = playerTile; } if (!playerTileChanged && changedTiles.empty()) return; const std::lock_guard lock(mMutex); if (playerTileChanged) for (auto& job : mJobs) job.mDistanceToPlayer = getManhattanDistance(job.mChangedTile, playerTile); for (const auto& changedTile : changedTiles) { if (mPushed[agentHalfExtents].insert(changedTile.first).second) { Job job; job.mAgentHalfExtents = agentHalfExtents; job.mNavMeshCacheItem = navMeshCacheItem; job.mChangedTile = changedTile.first; job.mTryNumber = 0; job.mChangeType = changedTile.second; job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); job.mProcessTime = job.mChangeType == ChangeType::update ? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); if (playerTileChanged) { mJobs.push_back(std::move(job)); } else { const auto it = std::upper_bound(mJobs.begin(), mJobs.end(), job); mJobs.insert(it, std::move(job)); } } } if (playerTileChanged) std::sort(mJobs.begin(), mJobs.end()); Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; if (!mJobs.empty()) mHasJob.notify_all(); } void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { if (mSettings.get().mWaitUntilMinDistanceToPlayer == 0) return; listener.setLabel("Building navigation mesh"); 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; } } 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() + getTotalThreadJobsUnsafe(); if (jobsLeft == 0) { minDistanceToPlayer = 0; return true; } minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles); for (const auto& [threadId, queue] : mThreadsQueues) minDistanceToPlayer = getMinDistanceTo(playerPosition, minDistanceToPlayer, queue.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() + getTotalThreadJobsUnsafe() == 0; }); } mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const { std::size_t jobs = 0; { const std::lock_guard lock(mMutex); jobs = mJobs.size() + getTotalThreadJobsUnsafe(); } stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs); mNavMeshTilesCache.reportStats(frameNumber, stats); } void AsyncNavMeshUpdater::process() noexcept { Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id(); Misc::setCurrentThreadIdlePriority(); while (!mShouldStop) { try { if (auto job = getNextJob()) { const auto processed = processJob(*job); unlockTile(job->mAgentHalfExtents, job->mChangedTile); if (!processed) repost(std::move(*job)); } 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(); } bool AsyncNavMeshUpdater::processJob(const Job& job) { Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")" " by thread=" << std::this_thread::get_id(); const auto start = std::chrono::steady_clock::now(); const auto firstStart = setFirstStart(start); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); if (!navMeshCacheItem) return true; const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto playerTile = *mPlayerTile.lockConst(); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache); if (recastMesh != nullptr) { Version navMeshVersion; { const auto locked = navMeshCacheItem->lockConst(); navMeshVersion.mGeneration = locked->getGeneration(); navMeshVersion.mRevision = locked->getNavMeshRevision(); } 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.mAgentHalfExtents, job.mChangedTile)); } else if (isSuccess(status) && status != UpdateNavMeshStatus::ignored) { const std::scoped_lock lock(mMutex); mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); } const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job, recastMesh.get()); using FloatMs = std::chrono::duration; const auto locked = navMeshCacheItem->lockConst(); Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Cache updated for agent=(" << job.mAgentHalfExtents << ")" << " tile=" << job.mChangedTile << " status=" << status << " generation=" << locked->getGeneration() << " revision=" << locked->getNavMeshRevision() << " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << " total_time=" << std::chrono::duration_cast(finish - firstStart).count() << "ms" " thread=" << std::this_thread::get_id(); return isSuccess(status); } std::optional AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); const auto threadId = std::this_thread::get_id(); auto& threadQueue = mThreadsQueues[threadId]; while (true) { const auto hasJob = [&] { return (!mJobs.empty() && mJobs.front().mProcessTime <= std::chrono::steady_clock::now()) || !threadQueue.mJobs.empty(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { mFirstStart.lock()->reset(); if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0) mDone.notify_all(); return std::nullopt; } Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " << threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id(); auto job = threadQueue.mJobs.empty() ? getJob(mJobs, mPushed, true) : getJob(threadQueue.mJobs, threadQueue.mPushed, false); if (!job) continue; const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile); if (owner == threadId) return job; postThreadJob(std::move(*job), mThreadsQueues[owner]); } } std::optional AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate) { const auto now = std::chrono::steady_clock::now(); if (jobs.front().mProcessTime > now) return {}; Job job = jobs.front(); jobs.pop_front(); if (changeLastUpdate && job.mChangeType == ChangeType::update) mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now; const auto it = pushed.find(job.mAgentHalfExtents); it->second.erase(job.mChangedTile); if (it->second.empty()) pushed.erase(it); 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); if (mSettings.get().mEnableWriteNavMeshToFile) if (const auto shared = job.mNavMeshCacheItem.lock()) writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) { const auto locked = mFirstStart.lock(); if (!*locked) *locked = value; return *locked.get(); } void AsyncNavMeshUpdater::repost(Job&& job) { if (mShouldStop || job.mTryNumber > 2) return; const std::lock_guard lock(mMutex); if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) { ++job.mTryNumber; mJobs.push_back(std::move(job)); mHasJob.notify_all(); } } void AsyncNavMeshUpdater::postThreadJob(Job&& job, Queue& queue) { if (queue.mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) { queue.mJobs.push_back(std::move(job)); mHasJob.notify_all(); } } std::thread::id AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) return std::this_thread::get_id(); auto locked = mProcessingTiles.lock(); auto agent = locked->find(agentHalfExtents); if (agent == locked->end()) { const auto threadId = std::this_thread::get_id(); locked->emplace(agentHalfExtents, std::map({{changedTile, threadId}})); return threadId; } auto tile = agent->second.find(changedTile); if (tile == agent->second.end()) { const auto threadId = std::this_thread::get_id(); agent->second.emplace(changedTile, threadId); return threadId; } return tile->second; } void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) return; auto locked = mProcessingTiles.lock(); auto agent = locked->find(agentHalfExtents); if (agent == locked->end()) return; auto tile = agent->second.find(changedTile); if (tile == agent->second.end()) return; agent->second.erase(tile); if (agent->second.empty()) locked->erase(agent); if (locked->empty()) mProcessed.notify_all(); } std::size_t AsyncNavMeshUpdater::getTotalJobs() const { const std::scoped_lock lock(mMutex); return mJobs.size() + getTotalThreadJobsUnsafe(); } std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const { return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), [] (auto r, const auto& v) { return r + v.second.mJobs.size(); }); } void AsyncNavMeshUpdater::cleanupLastUpdates() { const auto now = std::chrono::steady_clock::now(); const std::lock_guard lock(mMutex); for (auto agent = mLastUpdates.begin(); agent != mLastUpdates.end();) { for (auto tile = agent->second.begin(); tile != agent->second.end();) { if (now - tile->second > mSettings.get().mMinUpdateInterval) tile = agent->second.erase(tile); else ++tile; } if (agent->second.empty()) agent = mLastUpdates.erase(agent); else ++agent; } } } openmw-openmw-0.47.0/components/detournavigator/asyncnavmeshupdater.hpp000066400000000000000000000117401413061077700266220ustar00rootroot00000000000000#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 #include #include #include #include #include #include #include #include #include class dtNavMesh; namespace Loading { class Listener; } namespace DetourNavigator { enum class ChangeType { remove = 0, mixed = 1, add = 2, update = 3, }; inline 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); } class AsyncNavMeshUpdater { public: AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager); ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: struct Job { osg::Vec3f mAgentHalfExtents; std::weak_ptr mNavMeshCacheItem; TilePosition mChangedTile; unsigned mTryNumber; ChangeType mChangeType; int mDistanceToPlayer; int mDistanceToOrigin; std::chrono::steady_clock::time_point mProcessTime; std::tuple getPriority() const { return std::make_tuple(mProcessTime, mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); } friend inline bool operator <(const Job& lhs, const Job& rhs) { return lhs.getPriority() < rhs.getPriority(); } }; using Jobs = std::deque; using Pushed = std::map>; struct Queue { Jobs mJobs; Pushed mPushed; Queue() = default; }; 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; Jobs mJobs; std::map> mPushed; Misc::ScopeGuarded mPlayerTile; Misc::ScopeGuarded> mFirstStart; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map> mLastUpdates; std::set> mPresentTiles; std::map mThreadsQueues; std::vector mThreads; void process() noexcept; bool processJob(const Job& job); std::optional getNextJob(); std::optional getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate); void postThreadJob(Job&& job, Queue& queue); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value); void repost(Job&& job); std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); inline std::size_t getTotalJobs() const; inline std::size_t getTotalThreadJobsUnsafe() const; void cleanupLastUpdates(); int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); void waitUntilAllJobsDone(); }; } #endif openmw-openmw-0.47.0/components/detournavigator/bounds.hpp000066400000000000000000000005271413061077700240310ustar00rootroot00000000000000#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.47.0/components/detournavigator/cachedrecastmeshmanager.cpp000066400000000000000000000045141413061077700273530ustar00rootroot00000000000000#include "cachedrecastmeshmanager.hpp" #include "debug.hpp" namespace DetourNavigator { CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) : mImpl(settings, 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; mCached.lock()->reset(); return true; } bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { if (!mImpl.updateObject(id, transform, areaType)) return false; mCached.lock()->reset(); return true; } std::optional CachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mImpl.removeObject(id); if (object) mCached.lock()->reset(); return object; } bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { if (!mImpl.addWater(cellPosition, cellSize, transform)) return false; mCached.lock()->reset(); return true; } std::optional CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto water = mImpl.removeWater(cellPosition); if (water) mCached.lock()->reset(); return water; } std::shared_ptr CachedRecastMeshManager::getMesh() { std::shared_ptr cached = *mCached.lock(); if (cached != nullptr) return cached; cached = mImpl.getMesh(); *mCached.lock() = cached; return cached; } bool CachedRecastMeshManager::isEmpty() const { return mImpl.isEmpty(); } void CachedRecastMeshManager::reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion) { mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); } Version CachedRecastMeshManager::getVersion() const { return mImpl.getVersion(); } } openmw-openmw-0.47.0/components/detournavigator/cachedrecastmeshmanager.hpp000066400000000000000000000024101413061077700273510ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H #include "recastmeshmanager.hpp" #include "version.hpp" #include namespace DetourNavigator { class CachedRecastMeshManager { public: CachedRecastMeshManager(const Settings& settings, 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); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); std::optional removeWater(const osg::Vec2i& cellPosition); std::optional removeObject(const ObjectId id); std::shared_ptr getMesh(); bool isEmpty() const; void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); Version getVersion() const; private: RecastMeshManager mImpl; Misc::ScopeGuarded> mCached; }; } #endif openmw-openmw-0.47.0/components/detournavigator/debug.cpp000066400000000000000000000063201413061077700236150ustar00rootroot00000000000000#include "debug.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" #include #include #include namespace DetourNavigator { void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision) { 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::size_t count = 0; for (auto v : recastMesh.getVertices()) { if (count % 3 == 0) { if (count != 0) file << '\n'; file << 'v'; } file << ' ' << v; ++count; } file << '\n'; count = 0; for (auto v : recastMesh.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.47.0/components/detournavigator/debug.hpp000066400000000000000000000037341413061077700236300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H #include "tilebounds.hpp" #include "status.hpp" #include #include #include #include #include #include #include #include #include #include #include class dtNavMesh; namespace DetourNavigator { inline std::ostream& operator <<(std::ostream& stream, const TileBounds& value) { return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}"; } inline 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(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); } class RecastMesh; void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } #endif openmw-openmw-0.47.0/components/detournavigator/dtstatus.hpp000066400000000000000000000020751413061077700244120ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H #include #include #include namespace DetourNavigator { struct WriteDtStatus { dtStatus status; }; static const std::vector> dtStatuses { {DT_FAILURE, "DT_FAILURE"}, {DT_SUCCESS, "DT_SUCCESS"}, {DT_IN_PROGRESS, "DT_IN_PROGRESS"}, {DT_WRONG_MAGIC, "DT_WRONG_MAGIC"}, {DT_WRONG_VERSION, "DT_WRONG_VERSION"}, {DT_OUT_OF_MEMORY, "DT_OUT_OF_MEMORY"}, {DT_INVALID_PARAM, "DT_INVALID_PARAM"}, {DT_BUFFER_TOO_SMALL, "DT_BUFFER_TOO_SMALL"}, {DT_OUT_OF_NODES, "DT_OUT_OF_NODES"}, {DT_PARTIAL_RESULT, "DT_PARTIAL_RESULT"}, }; inline std::ostream& operator <<(std::ostream& stream, const WriteDtStatus& value) { for (const auto& status : dtStatuses) if (value.status & status.first) stream << status.second << " "; return stream; } } #endif openmw-openmw-0.47.0/components/detournavigator/exceptions.hpp000066400000000000000000000011761413061077700247210ustar00rootroot00000000000000#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.47.0/components/detournavigator/findrandompointaroundcircle.cpp000066400000000000000000000024131413061077700303140ustar00rootroot00000000000000#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 Settings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) return std::optional(); dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); if (startRef == 0) return std::optional(); dtPolyRef resultRef = 0; osg::Vec3f resultPosition; navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter, []() { return Misc::Rng::rollProbability(); }, &resultRef, resultPosition.ptr()); if (resultRef == 0) return std::optional(); return std::optional(resultPosition); } } openmw-openmw-0.47.0/components/detournavigator/findrandompointaroundcircle.hpp000066400000000000000000000007771413061077700303340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H #include "flags.hpp" #include #include class dtNavMesh; namespace DetourNavigator { struct Settings; std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings); } #endif openmw-openmw-0.47.0/components/detournavigator/findsmoothpath.cpp000066400000000000000000000135321413061077700255610ustar00rootroot00000000000000#include "findsmoothpath.hpp" #include #include #include namespace DetourNavigator { std::vector fixupCorridor(const std::vector& path, const std::vector& visited) { std::vector::const_reverse_iterator furthestVisited; // Find furthest common polygon. const auto it = std::find_if(path.rbegin(), path.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 == path.rend()) return path; const auto furthestPath = it.base() - 1; // Concatenate paths. // visited: a_1 ... a_n x b_1 ... b_n // furthestVisited ^ // path: C x D // ^ furthestPath // result: x b_n ... b_1 D std::vector result; result.reserve(static_cast(furthestVisited - visited.rbegin()) + static_cast(path.end() - furthestPath) - 1); std::copy(visited.rbegin(), furthestVisited + 1, std::back_inserter(result)); std::copy(furthestPath + 1, path.end(), std::back_inserter(result)); return result; } // 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::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery) { if (path.size() < 3) return path; // Get connected polygons const dtMeshTile* tile = nullptr; const dtPoly* poly = nullptr; if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly))) return path; 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, path.size()) - 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 path; std::vector result; const auto offset = cut - 1; result.reserve(1 + path.size() - offset); result.push_back(path.front()); std::copy(path.begin() + std::ptrdiff_t(offset), path.end(), std::back_inserter(result)); return result; } std::optional getSteerTarget(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path) { // 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.data(), static_cast(path.size()), 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.steerPos.ptr(), &steerPath[ns * 3]); result.steerPos.y() = startPos[1]; result.steerPosFlag = steerPathFlags[ns]; result.steerPosRef = steerPathPolys[ns]; return result; } dtPolyRef findNearestPolyExpanding(const dtNavMeshQuery& query, const dtQueryFilter& filter, const osg::Vec3f& center, const osg::Vec3f& halfExtents) { dtPolyRef ref = 0; for (int i = 0; i < 3; ++i) { const dtStatus status = query.findNearestPoly(center.ptr(), (halfExtents * (1 << i)).ptr(), &filter, &ref, nullptr); if (!dtStatusFailed(status) && ref != 0) break; } return ref; } } openmw-openmw-0.47.0/components/detournavigator/findsmoothpath.hpp000066400000000000000000000263751413061077700255770ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #include "dtstatus.hpp" #include "exceptions.hpp" #include "flags.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "debug.hpp" #include "status.hpp" #include "areatype.hpp" #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::vector fixupCorridor(const std::vector& path, 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::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery); struct SteerTarget { osg::Vec3f steerPos; unsigned char steerPosFlag; dtPolyRef steerPosRef; }; std::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path); template class OutputTransformIterator { public: OutputTransformIterator(OutputIterator& impl, const Settings& 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 findNearestPolyExpanding(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, const std::size_t maxSize) { int pathLen = 0; std::vector result(maxSize); const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, result.data(), &pathLen, static_cast(maxSize)); if (!dtStatusSucceed(status)) return {}; assert(pathLen >= 0); assert(static_cast(pathLen) <= maxSize); result.resize(static_cast(pathLen)); return {std::move(result)}; } 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 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.back(), 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 (!polygonPath.empty() && smoothPathSize < maxSmoothPathSize) { // Find location to steer towards. const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, slop, polygonPath); if (!steerTarget) break; const bool endOfPath = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_END); const bool offMeshConnection = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); // Find movement delta. const osg::Vec3f delta = steerTarget->steerPos - 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; polygonPath = fixupCorridor(polygonPath, result->mVisited); polygonPath = fixupShortcuts(polygonPath, navMeshQuery); // Handle end of path and off-mesh links when close enough. if (endOfPath && inRange(result->mResultPos, steerTarget->steerPos, slop)) { // Reached end of path. iterPos = targetPos; *out++ = iterPos; ++smoothPathSize; break; } else if (offMeshConnection && inRange(result->mResultPos, steerTarget->steerPos, slop)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; dtPolyRef polyRef = polygonPath.front(); std::size_t npos = 0; while (npos < polygonPath.size() && polyRef != steerTarget->steerPosRef) { prevRef = polyRef; polyRef = polygonPath[npos]; ++npos; } std::copy(polygonPath.begin() + std::ptrdiff_t(npos), polygonPath.end(), polygonPath.begin()); polygonPath.resize(polygonPath.size() - 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. if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), endPos.ptr(), &iterPos.y()))) return Status::GetPolyHeightFailed; iterPos.x() = endPos.x(); iterPos.z() = endPos.z(); } } if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), result->mResultPos.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, OutputIterator& out) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.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); dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); if (startRef == 0) return Status::StartPolygonNotFound; dtPolyRef endRef = findNearestPolyExpanding(navMeshQuery, queryFilter, end, halfExtents); if (endRef == 0) return Status::EndPolygonNotFound; const auto polygonPath = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, settings.mMaxPolygonPathSize); if (!polygonPath) return Status::FindPathOverPolygonsFailed; if (polygonPath->empty() || polygonPath->back() != endRef) return Status::Success; auto outTransform = OutputTransformIterator(out, settings); return makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, std::move(*polygonPath), settings.mMaxSmoothPathSize, outTransform); } } #endif openmw-openmw-0.47.0/components/detournavigator/flags.hpp000066400000000000000000000032211413061077700236250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H #include 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, }; inline 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; } struct WriteFlags { Flags mValue; friend inline 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; } } }; } #endif openmw-openmw-0.47.0/components/detournavigator/gettilespositions.hpp000066400000000000000000000047161413061077700263330ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" #include #include #include namespace DetourNavigator { template void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, const Settings& settings, Callback&& callback) { auto min = toNavMeshCoordinates(settings, aabbMin); auto max = toNavMeshCoordinates(settings, aabbMax); const auto border = getBorderSize(settings); min -= osg::Vec3f(border, border, border); max += osg::Vec3f(border, border, border); auto minTile = getTilePosition(settings, min); auto 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()); for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) callback(TilePosition {tileX, tileY}); } template void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, const Settings& settings, Callback&& callback) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); } template void getTilesPositions(const int cellSize, const btTransform& transform, const Settings& settings, Callback&& callback) { const auto halfCellSize = cellSize / 2; auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); auto 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())); getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); } } #endif openmw-openmw-0.47.0/components/detournavigator/makenavmesh.cpp000066400000000000000000000542551413061077700250400ustar00rootroot00000000000000#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 #include #include #include #include #include #include #include #include #include namespace { using namespace DetourNavigator; void initPolyMeshDetail(rcPolyMeshDetail& value) { value.meshes = nullptr; value.verts = nullptr; value.tris = nullptr; } struct PolyMeshDetailStackDeleter { void operator ()(rcPolyMeshDetail* value) const { rcFree(value->meshes); rcFree(value->verts); rcFree(value->tris); } }; using PolyMeshDetailStackPtr = std::unique_ptr; struct WaterBounds { osg::Vec3f mMin; osg::Vec3f mMax; }; WaterBounds getWaterBounds(const RecastMesh::Water& water, const Settings& settings, const osg::Vec3f& agentHalfExtents) { if (water.mCellSize == std::numeric_limits::max()) { const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); const auto min = toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(-1, -1, 0)))); const auto max = toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(1, 1, 0)))); return WaterBounds { osg::Vec3f(-std::numeric_limits::max(), min.y(), -std::numeric_limits::max()), osg::Vec3f(std::numeric_limits::max(), max.y(), std::numeric_limits::max()) }; } else { const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); const auto halfCellSize = water.mCellSize / 2.0f; return WaterBounds { toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(-halfCellSize, -halfCellSize, 0)))), toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(halfCellSize, halfCellSize, 0)))) }; } } 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; } rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) { rcConfig config; config.cs = settings.mCellSize; config.ch = settings.mCellHeight; config.walkableSlopeAngle = settings.mMaxSlope; config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); config.maxSimplificationError = settings.mMaxSimplificationError; config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; config.maxVertsPerPoly = settings.mMaxVertsPerPoly; config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; config.borderSize = settings.mBorderSize; config.width = settings.mTileSize + config.borderSize * 2; config.height = settings.mTileSize + config.borderSize * 2; rcVcopy(config.bmin, boundsMin.ptr()); rcVcopy(config.bmax, boundsMax.ptr()); config.bmin[0] -= getBorderSize(settings); config.bmin[2] -= getBorderSize(settings); config.bmax[0] += getBorderSize(settings); config.bmax[2] += getBorderSize(settings); config.tileSize = settings.mTileSize; return config; } void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, const float* bmax, const float cs, const float ch) { const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch); if (!result) throw NavigatorException("Failed to create heightfield for navmesh"); } bool rasterizeSolidObjectsTriangles(rcContext& context, const RecastMesh& recastMesh, const rcConfig& config, rcHeightfield& solid) { const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); std::vector areas(recastMesh.getAreaTypes().begin(), recastMesh.getAreaTypes().end()); rcClearUnwalkableTriangles( &context, config.walkableSlopeAngle, recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), recastMesh.getIndices().data(), static_cast(areas.size()), areas.data() ); return rcRasterizeTriangles( &context, recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), recastMesh.getIndices().data(), areas.data(), static_cast(areas.size()), solid, config.walkableClimb ); } void rasterizeWaterTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings, const rcConfig& config, rcHeightfield& solid) { const std::array areas {{AreaType_water, AreaType_water}}; for (const auto& water : recastMesh.getWater()) { const auto bounds = getWaterBounds(water, settings, agentHalfExtents); const osg::Vec2f tileBoundsMin( std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMin.x())), std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMin.z())) ); const osg::Vec2f tileBoundsMax( std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMax.x())), std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMax.z())) ); if (tileBoundsMax == tileBoundsMin) continue; const std::array vertices {{ osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMin.y()), osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMax.y()), osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMax.y()), osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMin.y()), }}; std::array convertedVertices; auto convertedVerticesIt = convertedVertices.begin(); for (const auto& vertex : vertices) convertedVerticesIt = std::copy(vertex.ptr(), vertex.ptr() + 3, convertedVerticesIt); const std::array indices {{ 0, 1, 2, 0, 2, 3, }}; const auto trianglesRasterized = rcRasterizeTriangles( &context, convertedVertices.data(), static_cast(convertedVertices.size() / 3), indices.data(), areas.data(), static_cast(areas.size()), solid, config.walkableClimb ); if (!trianglesRasterized) throw NavigatorException("Failed to create rasterize water triangles for navmesh"); } } bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const rcConfig& config, const Settings& settings, rcHeightfield& solid) { if (!rasterizeSolidObjectsTriangles(context, recastMesh, config, solid)) return false; rasterizeWaterTriangles(context, agentHalfExtents, recastMesh, settings, config, solid); return true; } 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 rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail) { rcCompactHeightfield compact; buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); erodeWalkableArea(context, config.walkableRadius, compact); buildDistanceField(context, compact); buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); rcContourSet contourSet; buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); if (contourSet.nconts == 0) return false; buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, polyMeshDetail); setPolyMeshFlags(polyMesh); return true; } NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const std::vector& offMeshConnections, const TilePosition& tile, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) { rcContext context; const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings); rcHeightfield solid; createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid)) return NavMeshData(); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); rcPolyMesh polyMesh; rcPolyMeshDetail polyMeshDetail; initPolyMeshDetail(polyMeshDetail); const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail); if (!fillPolyMesh(context, config, solid, polyMesh, polyMeshDetail)) return NavMeshData(); const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); const std::vector offMeshConDir(offMeshConnections.size(), 0); const std::vector offMeshConAreas = getOffMeshConAreas(offMeshConnections); const std::vector offMeshConFlags = getOffMeshFlags(offMeshConnections); dtNavMeshCreateParams params; params.verts = polyMesh.verts; params.vertCount = polyMesh.nverts; params.polys = polyMesh.polys; params.polyAreas = polyMesh.areas; params.polyFlags = polyMesh.flags; params.polyCount = polyMesh.npolys; params.nvp = polyMesh.nvp; params.detailMeshes = polyMeshDetail.meshes; params.detailVerts = polyMeshDetail.verts; params.detailVertsCount = polyMeshDetail.nverts; params.detailTris = polyMeshDetail.tris; params.detailTriCount = polyMeshDetail.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, agentHalfExtents); params.walkableRadius = getRadius(settings, agentHalfExtents); params.walkableClimb = getMaxClimb(settings); rcVcopy(params.bmin, polyMesh.bmin); rcVcopy(params.bmax, polyMesh.bmax); params.cs = config.cs; params.ch = config.ch; params.buildBvTree = true; params.userId = 0; 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); } template unsigned long getMinValuableBitsNumber(const T value) { unsigned long power = 0; while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) ++power; return power; } } namespace DetourNavigator { 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.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.mTileSize * settings.mCellSize; params.tileHeight = settings.mTileSize * settings.mCellSize; params.maxTiles = 1 << tilesBits; params.maxPolys = 1 << polysBits; NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); const auto status = navMesh->init(¶ms); if (!dtStatusSucceed(status)) throw NavigatorException("Failed to init navmesh"); return navMesh; } UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache) { Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Update NavMesh with multiple tiles:" << " agentHeight=" << getHeight(settings, agentHalfExtents) << " agentMaxClimb=" << getMaxClimb(settings) << " agentRadius=" << getRadius(settings, agentHalfExtents) << " changedTile=(" << changedTile << ")" << " playerTile=(" << playerTile << ")" << " changedTileDistance=" << getDistance(changedTile, playerTile); const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); if (!recastMesh) { Log(Debug::Debug) << "Ignore add tile: recastMesh is null"; return navMeshCacheItem->lock()->removeTile(changedTile); } auto recastMeshBounds = recastMesh->getBounds(); for (const auto& water : recastMesh->getWater()) { const auto waterBounds = getWaterBounds(water, settings, agentHalfExtents); recastMeshBounds.mMin.y() = std::min(recastMeshBounds.mMin.y(), waterBounds.mMin.y()); recastMeshBounds.mMax.y() = std::max(recastMeshBounds.mMax.y(), waterBounds.mMax.y()); } if (isEmpty(recastMeshBounds)) { Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; return navMeshCacheItem->lock()->removeTile(changedTile); } if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) { Log(Debug::Debug) << "Ignore add tile: too far from player"; return navMeshCacheItem->lock()->removeTile(changedTile); } auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); bool cached = static_cast(cachedNavMeshData); if (!cachedNavMeshData) { const auto tileBounds = makeTileBounds(settings, changedTile); const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), recastMeshBounds.mMin.y() - 1, tileBounds.mMin.y()); const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), recastMeshBounds.mMax.y() + 1, tileBounds.mMax.y()); auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, changedTile, tileBorderMin, tileBorderMax, settings); if (!navMeshData.mValue) { Log(Debug::Debug) << "Ignore add tile: NavMeshData is null"; return navMeshCacheItem->lock()->removeTile(changedTile); } cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, offMeshConnections, std::move(navMeshData)); if (!cachedNavMeshData) { Log(Debug::Debug) << "Navigator cache overflow"; return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData)); } } const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData)); return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult(); } } openmw-openmw-0.47.0/components/detournavigator/makenavmesh.hpp000066400000000000000000000025671413061077700250440ustar00rootroot00000000000000#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 #include class dtNavMesh; namespace DetourNavigator { class RecastMesh; struct Settings; 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; } NavMeshPtr makeEmptyNavMesh(const Settings& settings); UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache); } #endif openmw-openmw-0.47.0/components/detournavigator/navigator.cpp000066400000000000000000000031621413061077700245220ustar00rootroot00000000000000#include "findrandompointaroundcircle.hpp" #include "navigator.hpp" #include "raycast.hpp" namespace DetourNavigator { std::optional Navigator::findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const { const auto navMesh = getNavMesh(agentHalfExtents); if (!navMesh) return std::optional(); const auto settings = getSettings(); const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); if (!result) return std::optional(); return std::optional(fromNavMeshCoordinates(settings, *result)); } std::optional Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags) const { const auto navMesh = getNavMesh(agentHalfExtents); if (navMesh == nullptr) return {}; const auto settings = getSettings(); const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, end), includeFlags, settings); if (!result) return {}; return fromNavMeshCoordinates(settings, *result); } } openmw-openmw-0.47.0/components/detournavigator/navigator.hpp000066400000000000000000000267461413061077700245440ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #include "findsmoothpath.hpp" #include "flags.hpp" #include "settings.hpp" #include "objectid.hpp" #include "navmeshcacheitem.hpp" #include "recastmeshtiles.hpp" #include "waitconditiontype.hpp" #include namespace ESM { struct Cell; struct Pathgrid; } namespace Loading { class Listener; } namespace DetourNavigator { struct ObjectShapes { osg::ref_ptr mShapeInstance; ObjectShapes(const osg::ref_ptr& shapeInstance) : mShapeInstance(shapeInstance) {} }; struct DoorShapes : ObjectShapes { osg::Vec3f mConnectionStart; osg::Vec3f mConnectionEnd; DoorShapes(const osg::ref_ptr& shapeInstance, const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) : ObjectShapes(shapeInstance) , 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 agentHalfExtents allows to setup bounding cylinder for each agent, for each different half extents * there is different navmesh. */ virtual void addAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief removeAgent should be called for each agent even if all of them has same half extents * @param agentHalfExtents allows determine which agent to remove */ virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief addObject is used to add object represented by single btHeightfieldTerrainShape and btTransform. * @param id is used to distinguish different objects. * @param holder shape wrapper to keep shape lifetime after object is removed. * @param shape must be wrapped by holder. * @param transform allows to setup object 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 osg::ref_ptr& holder, const btHeightfieldTerrainShape& shape, const btTransform& transform) = 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 level set z coordinate of water surface at the scene. * @param transform 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, const int cellSize, const btScalar level, const btTransform& transform) = 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 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 findPath fills output iterator with points of scene surfaces to be used for actor to walk through. * @param agentHalfExtents 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. * @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 Status findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourNavigator::AreaCosts& areaCosts, OutputIterator& out) const { static_assert( std::is_same< typename std::iterator_traits::iterator_category, std::output_iterator_tag >::value, "out is not an OutputIterator" ); const auto navMesh = getNavMesh(agentHalfExtents); if (!navMesh) return Status::NavMeshNotFound; const auto settings = getSettings(); return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, out); } /** * @brief getNavMesh returns navmesh for specific agent half extents * @return navmesh */ virtual SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) 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; /** * @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location. * @param agentHalfExtents allows to find navmesh for given actor. * @param start path from given point. * @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 osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; /** * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. * @param agentHalfExtents 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 osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags) const; virtual RecastMeshTiles getRecastMeshTiles() = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0; }; } #endif openmw-openmw-0.47.0/components/detournavigator/navigatorimpl.cpp000066400000000000000000000200761413061077700254070ustar00rootroot00000000000000#include "navigatorimpl.hpp" #include "debug.hpp" #include "settingsutils.hpp" #include #include namespace DetourNavigator { NavigatorImpl::NavigatorImpl(const Settings& settings) : mSettings(settings) , mNavMeshManager(mSettings) , mUpdatesEnabled(true) { } void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents) { if(agentHalfExtents.length2() <= 0) return; ++mAgents[agentHalfExtents]; mNavMeshManager.addAgent(agentHalfExtents); } void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents) { const auto it = mAgents.find(agentHalfExtents); if (it == mAgents.end()) return; if (it->second > 0) --it->second; } bool NavigatorImpl::addObject(const ObjectId id, const osg::ref_ptr& holder, const btHeightfieldTerrainShape& shape, const btTransform& transform) { const CollisionShape collisionShape {holder, shape}; return mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); } bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { const ObjectId avoidId(avoidShape); const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; if (mNavMeshManager.addObject(avoidId, collisionShape, 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, shapes.mConnectionStart); const osg::Vec3f end = toNavMeshCoordinates(mSettings, 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->getCollisionShape()}; bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { const ObjectId avoidId(avoidShape); const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; if (mNavMeshManager.updateObject(avoidId, collisionShape, 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, const int cellSize, const btScalar level, const btTransform& transform) { return mNavMeshManager.addWater(cellPosition, cellSize, btTransform(transform.getBasis(), btVector3(transform.getOrigin().x(), transform.getOrigin().y(), level))); } bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) { return mNavMeshManager.removeWater(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, src), toNavMeshCoordinates(mSettings, 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, toNavMeshCoordinates(mSettings, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; 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 osg::Vec3f& agentHalfExtents) const { return mNavMeshManager.getNavMesh(agentHalfExtents); } 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() { 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) * getMaxNavmeshAreaRadius(settings); } } openmw-openmw-0.47.0/components/detournavigator/navigatorimpl.hpp000066400000000000000000000061661413061077700254200ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H #include "navigator.hpp" #include "navmeshmanager.hpp" #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); void addAgent(const osg::Vec3f& agentHalfExtents) override; void removeAgent(const osg::Vec3f& agentHalfExtents) override; bool addObject(const ObjectId id, const osg::ref_ptr& holder, const btHeightfieldTerrainShape& shape, const btTransform& transform) 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, const int cellSize, const btScalar level, const btTransform& transform) override; bool removeWater(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 osg::Vec3f& agentHalfExtents) const override; std::map getNavMeshes() const override; const Settings& getSettings() const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; RecastMeshTiles getRecastMeshTiles() 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.47.0/components/detournavigator/navigatorstub.hpp000066400000000000000000000062161413061077700254300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H #include "navigator.hpp" namespace Loading { class Listener; } namespace DetourNavigator { class NavigatorStub final : public Navigator { public: NavigatorStub() = default; void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} bool addObject(const ObjectId /*id*/, const osg::ref_ptr& /*holder*/, const btHeightfieldTerrainShape& /*shape*/, const btTransform& /*transform*/) override { return false; } 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*/, const int /*cellSize*/, const btScalar /*level*/, const btTransform& /*transform*/) override { return false; } bool removeWater(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 updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; void setUpdatesEnabled(bool /*enabled*/) override {} void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override { return mEmptyNavMeshCacheItem; } std::map getNavMeshes() const override { return std::map(); } const Settings& getSettings() const override { return mDefaultSettings; } void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} RecastMeshTiles getRecastMeshTiles() override { return {}; } float getMaxNavmeshAreaRealRadius() const override { return std::numeric_limits::max(); } private: Settings mDefaultSettings {}; SharedNavMeshCacheItem mEmptyNavMeshCacheItem; }; } #endif openmw-openmw-0.47.0/components/detournavigator/navmeshcacheitem.hpp000066400000000000000000000153531413061077700260460ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #include "sharednavmesh.hpp" #include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "dtstatus.hpp" #include "navmeshtileview.hpp" #include #include #include 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; } 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)); } }; inline unsigned char* getRawData(NavMeshData& navMeshData) { return navMeshData.mValue.get(); } inline unsigned char* getRawData(NavMeshTilesCache::Value& cachedNavMeshData) { return cachedNavMeshData.get().mValue; } inline int getSize(const NavMeshData& navMeshData) { return navMeshData.mSize; } inline int getSize(const NavMeshTilesCache::Value& cachedNavMeshData) { return cachedNavMeshData.get().mSize; } class NavMeshCacheItem { public: NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation) : mImpl(impl), mGeneration(generation), mNavMeshRevision(0) { } const dtNavMesh& getImpl() const { return *mImpl; } std::size_t getGeneration() const { return mGeneration; } std::size_t getNavMeshRevision() const { return mNavMeshRevision; } template UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData) { const dtMeshTile* currentTile = getTile(position); if (currentTile != nullptr && asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(getRawData(navMeshData))) { return UpdateNavMeshStatus::ignored; } const auto removed = removeTileImpl(position); const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData)); if (dtStatusSucceed(addStatus)) { setUsedTile(position, std::forward(navMeshData)); return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); } else { if (removed) removeUsedTile(position); return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult(); } } UpdateNavMeshStatus removeTile(const TilePosition& position) { const auto removed = removeTileImpl(position); if (removed) removeUsedTile(position); return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } private: NavMeshPtr mImpl; std::size_t mGeneration; std::size_t mNavMeshRevision; std::map> mUsedTiles; void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value) { mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData()); ++mNavMeshRevision; } void setUsedTile(const TilePosition& tilePosition, NavMeshData value) { mUsedTiles[tilePosition] = std::make_pair(NavMeshTilesCache::Value(), std::move(value)); ++mNavMeshRevision; } void removeUsedTile(const TilePosition& tilePosition) { mUsedTiles.erase(tilePosition); ++mNavMeshRevision; } dtStatus addTileImpl(unsigned char* data, int size) { const int doNotTransferOwnership = 0; const dtTileRef lastRef = 0; dtTileRef* const result = nullptr; return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result); } bool removeTileImpl(const TilePosition& position) { const int layer = 0; const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer); if (tileRef == 0) return false; unsigned char** const data = nullptr; int* const dataSize = nullptr; return dtStatusSucceed(mImpl->removeTile(tileRef, data, dataSize)); } const dtMeshTile* getTile(const TilePosition& position) const { const int layer = 0; return mImpl->getTileAt(position.x(), position.y(), layer); } }; using GuardedNavMeshCacheItem = Misc::ScopeGuarded; using SharedNavMeshCacheItem = std::shared_ptr; } #endif openmw-openmw-0.47.0/components/detournavigator/navmeshdata.hpp000066400000000000000000000012651413061077700250320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H #include #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; NavMeshData() = default; NavMeshData(unsigned char* value, int size) : mValue(value) , mSize(size) {} }; } #endif openmw-openmw-0.47.0/components/detournavigator/navmeshmanager.cpp000066400000000000000000000243131413061077700255250ustar00rootroot00000000000000#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 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 { NavMeshManager::NavMeshManager(const Settings& settings) : mSettings(settings) , mRecastMeshManager(settings) , mOffMeshConnectionsManager(settings) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { const btCollisionShape& collisionShape = shape.getShape(); if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) return false; addChangedTiles(collisionShape, transform, ChangeType::add); return true; } 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) { addChangedTile(tile, ChangeType::update); }); } 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, const int cellSize, const btTransform& transform) { if (!mRecastMeshManager.addWater(cellPosition, cellSize, transform)) return false; addChangedTiles(cellSize, transform, ChangeType::add); return true; } bool NavMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto water = mRecastMeshManager.removeWater(cellPosition); if (!water) return false; addChangedTiles(water->mCellSize, water->mTransform, ChangeType::remove); return true; } void NavMeshManager::addAgent(const osg::Vec3f& agentHalfExtents) { auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentHalfExtents, std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); Log(Debug::Debug) << "cache add for agent=" << agentHalfExtents; } bool NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) { const auto it = mCache.find(agentHalfExtents); if (it == mCache.end()) return true; if (!resetIfUnique(it->second)) return false; mCache.erase(agentHalfExtents); mChangedTiles.erase(agentHalfExtents); mPlayerTile.erase(agentHalfExtents); mLastRecastMeshManagerRevision.erase(agentHalfExtents); 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, start); const auto endTilePosition = getTilePosition(mSettings, 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(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents]; auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() && lastPlayerTile->second == playerTile) return; lastRevision = mRecastMeshManager.getRevision(); if (lastPlayerTile == mPlayerTile.end()) lastPlayerTile = mPlayerTile.insert(std::make_pair(agentHalfExtents, playerTile)).first; else lastPlayerTile->second = playerTile; std::map tilesToPost; const auto cached = getCached(agentHalfExtents); if (!cached) { std::ostringstream stream; stream << "Agent with half extents is not found: " << agentHalfExtents; throw InvalidArgument(stream.str()); } const auto changedTiles = mChangedTiles.find(agentHalfExtents); { 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, ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); else recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); if (changedTiles != mChangedTiles.end()) changedTiles->second.clear(); Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << " playerTile=" << lastPlayerTile->second << " recastMeshManagerRevision=" << lastRevision; } void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { mAsyncNavMeshUpdater.wait(listener, waitConditionType); } SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const { return getCached(agentHalfExtents); } std::map NavMeshManager::getNavMeshes() const { return mCache; } void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mAsyncNavMeshUpdater.reportStats(frameNumber, stats); } RecastMeshTiles NavMeshManager::getRecastMeshTiles() { std::vector tiles; mRecastMeshManager.forEachTile( [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); RecastMeshTiles result; std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); return result; } void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } void NavMeshManager::addChangedTiles(const int cellSize, const btTransform& transform, const ChangeType changeType) { if (cellSize == std::numeric_limits::max()) return; getTilesPositions(cellSize, transform, mSettings, [&] (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 osg::Vec3f& agentHalfExtents) const { const auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) return cached->second; return SharedNavMeshCacheItem(); } } openmw-openmw-0.47.0/components/detournavigator/navmeshmanager.hpp000066400000000000000000000053661413061077700255410ustar00rootroot00000000000000#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 #include #include #include class dtNavMesh; namespace DetourNavigator { class NavMeshManager { public: NavMeshManager(const Settings& settings); 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 osg::Vec3f& agentHalfExtents); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); bool removeWater(const osg::Vec2i& cellPosition); bool reset(const osg::Vec3f& agentHalfExtents); void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType); void removeOffMeshConnections(const ObjectId id); void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const; std::map getNavMeshes() const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; RecastMeshTiles getRecastMeshTiles(); private: const Settings& mSettings; 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 btTransform& transform, const ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); SharedNavMeshCacheItem getCached(const osg::Vec3f& agentHalfExtents) const; }; } #endif openmw-openmw-0.47.0/components/detournavigator/navmeshtilescache.cpp000066400000000000000000000124351413061077700262210ustar00rootroot00000000000000#include "navmeshtilescache.hpp" #include #include namespace DetourNavigator { namespace { inline std::size_t getSize(const RecastMesh& recastMesh, const std::vector& offMeshConnections) { const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int); const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float); const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType); const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water); const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection); return indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize; } } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), mHitCount(0), mGetCount(0) {} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) { const std::lock_guard lock(mMutex); ++mGetCount; const auto tile = mValues.find(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyView(recastMesh, offMeshConnections))); if (tile == mValues.end()) return Value(); acquireItemUnsafe(tile->second); ++mHitCount; return Value(*this, tile->second); } NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value) { const auto itemSize = static_cast(value.mSize) + getSize(recastMesh, offMeshConnections); const std::lock_guard lock(mMutex); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); NavMeshKey navMeshKey { RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, offMeshConnections }; const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues.emplace(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyRef(iterator->mNavMeshKey)), iterator); if (!emplaced.second) { mFreeItems.erase(iterator); acquireItemUnsafe(emplaced.first->second); ++mGetCount; ++mHitCount; return Value(*this, emplaced.first->second); } iterator->mNavMeshData = 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 NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const { const Stats stats = getStats(); out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles); 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::make_tuple(item.mAgentHalfExtents, item.mChangedTile, NavMeshKeyRef(item.mNavMeshKey))); 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.47.0/components/detournavigator/navmeshtilescache.hpp000066400000000000000000000160721413061077700262270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #include "offmeshconnection.hpp" #include "navmeshdata.hpp" #include "recastmesh.hpp" #include "tileposition.hpp" #include #include #include #include #include #include #include namespace osg { class Stats; } namespace DetourNavigator { struct NavMeshDataRef { unsigned char* mValue; int mSize; }; struct RecastMeshData { std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; std::vector mWater; }; inline bool operator <(const RecastMeshData& lhs, const RecastMeshData& rhs) { return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); } inline bool operator <(const RecastMeshData& lhs, const RecastMesh& rhs) { return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); } inline bool operator <(const RecastMesh& lhs, const RecastMeshData& rhs) { return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); } struct NavMeshKey { RecastMeshData mRecastMesh; std::vector mOffMeshConnections; }; inline bool operator <(const NavMeshKey& lhs, const NavMeshKey& rhs) { return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); } struct NavMeshKeyRef { std::reference_wrapper mRef; explicit NavMeshKeyRef(const NavMeshKey& ref) : mRef(ref) {} }; inline bool operator <(const NavMeshKeyRef& lhs, const NavMeshKeyRef& rhs) { return lhs.mRef.get() < rhs.mRef.get(); } struct NavMeshKeyView { std::reference_wrapper mRecastMesh; std::reference_wrapper> mOffMeshConnections; NavMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} }; inline bool operator <(const NavMeshKeyView& lhs, const NavMeshKey& rhs) { return std::tie(lhs.mRecastMesh.get(), lhs.mOffMeshConnections.get()) < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); } inline bool operator <(const NavMeshKey& lhs, const NavMeshKeyView& rhs) { return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) < std::tie(rhs.mRecastMesh.get(), rhs.mOffMeshConnections.get()); } template inline bool operator <(const NavMeshKeyRef& lhs, const R& rhs) { return lhs.mRef.get() < rhs; } template inline bool operator <(const L& lhs, const NavMeshKeyRef& rhs) { return lhs < rhs.mRef.get(); } template inline bool operator <(const std::tuple& lhs, const std::tuple& rhs) { const auto left = std::tie(std::get<0>(lhs), std::get<1>(lhs)); const auto right = std::tie(std::get<0>(rhs), std::get<1>(rhs)); return std::tie(left, std::get<2>(lhs)) < std::tie(right, std::get<2>(rhs)); } class NavMeshTilesCache { public: struct Item { std::atomic mUseCount; osg::Vec3f mAgentHalfExtents; TilePosition mChangedTile; NavMeshKey mNavMeshKey; NavMeshData mNavMeshData; std::size_t mSize; Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshKey&& navMeshKey, std::size_t size) : mUseCount(0) , mAgentHalfExtents(agentHalfExtents) , mChangedTile(changedTile) , mNavMeshKey(navMeshKey) , 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; } NavMeshDataRef get() const { return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize}; } 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 osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections); Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value); Stats getStats() const; void reportStats(unsigned int frameNumber, osg::Stats& stats) 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); }; } #endif openmw-openmw-0.47.0/components/detournavigator/navmeshtileview.cpp000066400000000000000000000135431413061077700257460ustar00rootroot00000000000000#include "navmeshtileview.hpp" #include #include #include #include #include #include namespace { template struct Ref { T& mRef; explicit Ref(T& ref) : mRef(ref) {} friend bool operator==(const Ref& lhs, const Ref& rhs) { return lhs.mRef == rhs.mRef; } }; template struct ArrayRef { T (&mRef)[size]; explicit ArrayRef(T (&ref)[size]) : 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; explicit Span(T* data, int size) : 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); } }; auto makeTuple(const dtMeshHeader& v) { 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 ); } auto makeTuple(const dtPoly& v) { return std::tuple(ArrayRef(v.verts), ArrayRef(v.neis), v.flags, v.vertCount, v.areaAndtype); } auto makeTuple(const dtPolyDetail& v) { return std::tuple(v.vertBase, v.triBase, v.vertCount, v.triCount); } auto makeTuple(const dtBVNode& v) { return std::tuple(ArrayRef(v.bmin), ArrayRef(v.bmax), v.i); } auto makeTuple(const dtOffMeshConnection& v) { return std::tuple(ArrayRef(v.pos), v.rad, v.poly, v.flags, v.side, v.userId); } 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) ); } } template inline auto operator==(const T& lhs, const T& rhs) -> std::enable_if_t, void>, bool> { 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) { return makeTuple(lhs) == makeTuple(rhs); } } openmw-openmw-0.47.0/components/detournavigator/navmeshtileview.hpp000066400000000000000000000015511413061077700257470ustar00rootroot00000000000000#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); }; NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data); NavMeshTileConstView asNavMeshTileConstView(const dtMeshTile& tile); } #endif openmw-openmw-0.47.0/components/detournavigator/objectid.hpp000066400000000000000000000020211413061077700243110ustar00rootroot00000000000000#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)) { } 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.47.0/components/detournavigator/offmeshconnection.hpp000066400000000000000000000010571413061077700262450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H #include "areatype.hpp" #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.47.0/components/detournavigator/offmeshconnectionsmanager.cpp000066400000000000000000000060041413061077700277530ustar00rootroot00000000000000#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 Settings& 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) { std::vector result; const auto values = mValues.lock(); 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.47.0/components/detournavigator/offmeshconnectionsmanager.hpp000066400000000000000000000017661413061077700277720ustar00rootroot00000000000000#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: OffMeshConnectionsManager(const Settings& settings); void add(const ObjectId id, const OffMeshConnection& value); std::set remove(const ObjectId id); std::vector get(const TilePosition& tilePosition); private: struct Values { std::multimap mById; std::map> mByTilePosition; }; const Settings& mSettings; Misc::ScopeGuarded mValues; }; } #endif openmw-openmw-0.47.0/components/detournavigator/oscillatingrecastmeshobject.cpp000066400000000000000000000042461413061077700303120ustar00rootroot00000000000000#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.47.0/components/detournavigator/oscillatingrecastmeshobject.hpp000066400000000000000000000017501413061077700303140ustar00rootroot00000000000000#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.47.0/components/detournavigator/raycast.cpp000066400000000000000000000026671413061077700242070ustar00rootroot00000000000000#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 Settings& 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.47.0/components/detournavigator/raycast.hpp000066400000000000000000000007031413061077700242010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H #include "flags.hpp" #include #include class dtNavMesh; namespace DetourNavigator { struct Settings; std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings); } #endif openmw-openmw-0.47.0/components/detournavigator/recastallocutils.hpp000066400000000000000000000040271413061077700261130ustar00rootroot00000000000000#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.47.0/components/detournavigator/recastglobalallocator.hpp000066400000000000000000000034731413061077700271050ustar00rootroot00000000000000#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.47.0/components/detournavigator/recastmesh.cpp000066400000000000000000000021011413061077700246560ustar00rootroot00000000000000#include "recastmesh.hpp" #include "exceptions.hpp" #include namespace DetourNavigator { RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, std::vector areaTypes, std::vector water) : mGeneration(generation) , mRevision(revision) , mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) , mWater(std::move(water)) { if (getTrianglesCount() != mAreaTypes.size()) throw InvalidArgument("Number of flags doesn't match number of triangles: triangles=" + std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size())); if (getVerticesCount()) rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBounds.mMin.ptr(), mBounds.mMax.ptr()); mIndices.shrink_to_fit(); mVertices.shrink_to_fit(); mAreaTypes.shrink_to_fit(); mWater.shrink_to_fit(); } } openmw-openmw-0.47.0/components/detournavigator/recastmesh.hpp000066400000000000000000000044411413061077700246740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #include "areatype.hpp" #include "bounds.hpp" #include #include #include #include #include #include namespace DetourNavigator { class RecastMesh { public: struct Water { int mCellSize; btTransform mTransform; }; RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, std::vector areaTypes, std::vector water); std::size_t getGeneration() const { return mGeneration; } std::size_t getRevision() const { return mRevision; } const std::vector& getIndices() const { return mIndices; } const std::vector& getVertices() const { return mVertices; } const std::vector& getAreaTypes() const { return mAreaTypes; } const std::vector& getWater() const { return mWater; } std::size_t getVerticesCount() const { return mVertices.size() / 3; } std::size_t getTrianglesCount() const { return mIndices.size() / 3; } const Bounds& getBounds() const { return mBounds; } private: std::size_t mGeneration; std::size_t mRevision; std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; std::vector mWater; Bounds mBounds; }; inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) { return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); } inline bool operator <(const RecastMesh& lhs, const RecastMesh& rhs) { return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); } } #endif openmw-openmw-0.47.0/components/detournavigator/recastmeshbuilder.cpp000066400000000000000000000227631413061077700262450ustar00rootroot00000000000000#include "recastmeshbuilder.hpp" #include "debug.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "exceptions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; namespace { void optimizeRecastMesh(std::vector& indices, std::vector& vertices) { std::vector> uniqueVertices; uniqueVertices.reserve(vertices.size() / 3); for (std::size_t i = 0, n = vertices.size() / 3; i < n; ++i) uniqueVertices.emplace_back(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]); std::sort(uniqueVertices.begin(), uniqueVertices.end()); const auto end = std::unique(uniqueVertices.begin(), uniqueVertices.end()); uniqueVertices.erase(end, uniqueVertices.end()); if (uniqueVertices.size() == vertices.size() / 3) return; for (std::size_t i = 0, n = indices.size(); i < n; ++i) { const auto index = indices[i]; const auto vertex = std::make_tuple(vertices[index * 3], vertices[index * 3 + 1], vertices[index * 3 + 2]); const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); assert(it != uniqueVertices.end()); assert(*it == vertex); indices[i] = std::distance(uniqueVertices.begin(), it); } vertices.resize(uniqueVertices.size() * 3); for (std::size_t i = 0, n = uniqueVertices.size(); i < n; ++i) { vertices[i * 3] = std::get<0>(uniqueVertices[i]); vertices[i * 3 + 1] = std::get<1>(uniqueVertices[i]); vertices[i * 3 + 2] = std::get<2>(uniqueVertices[i]); } } } RecastMeshBuilder::RecastMeshBuilder(const Settings& settings, const TileBounds& bounds) : mSettings(settings) , mBounds(bounds) { mBounds.mMin /= mSettings.get().mRecastScaleFactor; mBounds.mMax /= mSettings.get().mRecastScaleFactor; } 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* triangle, int, int) { for (std::size_t i = 3; i > 0; --i) addTriangleVertex(triangle[i - 1]); mAreaTypes.push_back(areaType); })); } void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const AreaType areaType) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) addTriangleVertex(triangle[i]); mAreaTypes.push_back(areaType); })); } void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { const auto indexOffset = static_cast(mVertices.size() / 3); for (int vertex = 0, count = shape.getNumVertices(); vertex < count; ++vertex) { btVector3 position; shape.getVertex(vertex, position); addVertex(transform(position)); } const 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, }}; std::transform(indices.begin(), indices.end(), std::back_inserter(mIndices), [&] (int index) { return index + indexOffset; }); std::generate_n(std::back_inserter(mAreaTypes), 12, [=] { return areaType; }); } void RecastMeshBuilder::addWater(const int cellSize, const btTransform& transform) { mWater.push_back(RecastMesh::Water {cellSize, transform}); } std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { optimizeRecastMesh(mIndices, mVertices); std::sort(mWater.begin(), mWater.end()); return std::make_shared(generation, revision, std::move(mIndices), std::move(mVertices), std::move(mAreaTypes), std::move(mWater)); } 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); } void RecastMeshBuilder::addTriangleVertex(const btVector3& worldPosition) { mIndices.push_back(static_cast(mVertices.size() / 3)); addVertex(worldPosition); } void RecastMeshBuilder::addVertex(const btVector3& worldPosition) { const auto navMeshPosition = toNavMeshCoordinates(mSettings, Misc::Convert::makeOsgVec3f(worldPosition)); mVertices.push_back(navMeshPosition.x()); mVertices.push_back(navMeshPosition.y()); mVertices.push_back(navMeshPosition.z()); } } openmw-openmw-0.47.0/components/detournavigator/recastmeshbuilder.hpp000066400000000000000000000035571413061077700262520ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H #include "recastmesh.hpp" #include "tilebounds.hpp" #include class btBoxShape; class btCollisionShape; class btCompoundShape; class btConcaveShape; class btHeightfieldTerrainShape; class btTriangleCallback; namespace DetourNavigator { struct Settings; class RecastMeshBuilder { public: RecastMeshBuilder(const Settings& settings, const TileBounds& bounds); void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); 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 int mCellSize, const btTransform& transform); std::shared_ptr create(std::size_t generation, std::size_t revision) &&; private: std::reference_wrapper mSettings; TileBounds mBounds; std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; std::vector mWater; void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, btTriangleCallback&& callback); void addTriangleVertex(const btVector3& worldPosition); void addVertex(const btVector3& worldPosition); }; } #endif openmw-openmw-0.47.0/components/detournavigator/recastmeshmanager.cpp000066400000000000000000000116701413061077700262240ustar00rootroot00000000000000#include "recastmeshmanager.hpp" #include "recastmeshbuilder.hpp" namespace DetourNavigator { RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) : mSettings(settings) , 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; const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); mObjects.emplace_hint(object, id, iterator); ++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()}; mObjectsOrder.erase(object->second); mObjects.erase(object); ++mRevision; return result; } bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { const std::lock_guard lock(mMutex); const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform}); if (!mWater.emplace(cellPosition, iterator).second) { mWaterOrder.erase(iterator); 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; const auto result = *water->second; mWaterOrder.erase(water->second); mWater.erase(water); return result; } std::shared_ptr RecastMeshManager::getMesh() { RecastMeshBuilder builder(mSettings, mTileBounds); using Object = std::tuple< osg::ref_ptr, std::reference_wrapper, btTransform, AreaType >; std::vector objects; std::size_t revision; { const std::lock_guard lock(mMutex); for (const auto& v : mWaterOrder) builder.addWater(v.mCellSize, v.mTransform); objects.reserve(mObjectsOrder.size()); for (const auto& object : mObjectsOrder) { const RecastMeshObject& impl = object.getImpl(); objects.emplace_back(impl.getHolder(), impl.getShape(), impl.getTransform(), impl.getAreaType()); } revision = mRevision; } for (const auto& [holder, shape, transform, areaType] : objects) builder.addObject(shape, transform, areaType); return std::move(builder).create(mGeneration, revision); } bool RecastMeshManager::isEmpty() const { const std::lock_guard lock(mMutex); return mObjects.empty(); } void RecastMeshManager::reportNavMeshChange(Version recastMeshVersion, 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.47.0/components/detournavigator/recastmeshmanager.hpp000066400000000000000000000043341413061077700262300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #include "oscillatingrecastmeshobject.hpp" #include "objectid.hpp" #include "version.hpp" #include #include #include #include #include #include #include class btCollisionShape; namespace DetourNavigator { struct Settings; class RecastMesh; struct RemovedRecastMeshObject { std::reference_wrapper mShape; btTransform mTransform; }; class RecastMeshManager { public: struct Water { int mCellSize = 0; btTransform mTransform; }; RecastMeshManager(const Settings& settings, 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); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); std::optional removeWater(const osg::Vec2i& cellPosition); std::optional removeObject(const ObjectId id); std::shared_ptr getMesh(); bool isEmpty() const; void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); Version getVersion() const; private: struct Report { std::size_t mRevision; Version mNavMeshVersion; }; const Settings& mSettings; const std::size_t mGeneration; const TileBounds mTileBounds; mutable std::mutex mMutex; std::size_t mRevision = 0; std::list mObjectsOrder; std::map::iterator> mObjects; std::list mWaterOrder; std::map::iterator> mWater; std::optional mLastNavMeshReportedChange; std::optional mLastNavMeshReport; }; } #endif openmw-openmw-0.47.0/components/detournavigator/recastmeshobject.cpp000066400000000000000000000057621413061077700260650ustar00rootroot00000000000000#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 osg::ref_ptr& holder, const btCompoundShape& shape, const AreaType areaType) { std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) { const CollisionShape collisionShape {holder, *shape.getChildShape(i)}; result.emplace_back(collisionShape, shape.getChildTransform(i), areaType); } return result; } std::vector makeChildrenObjects(const osg::ref_ptr& holder, const btCollisionShape& shape, const AreaType areaType) { if (shape.isCompound()) return makeChildrenObjects(holder, static_cast(shape), areaType); return std::vector(); } } RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) : mHolder(shape.getHolder()) , mShape(shape.getShape()) , mTransform(transform) , mAreaType(areaType) , mLocalScaling(mShape.get().getLocalScaling()) , mChildren(makeChildrenObjects(mHolder, mShape.get(), mAreaType)) { } bool RecastMeshObject::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; } } openmw-openmw-0.47.0/components/detournavigator/recastmeshobject.hpp000066400000000000000000000035211413061077700260610ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #include "areatype.hpp" #include #include #include #include #include class btCollisionShape; class btCompoundShape; namespace DetourNavigator { class CollisionShape { public: CollisionShape(osg::ref_ptr holder, const btCollisionShape& shape) : mHolder(std::move(holder)) , mShape(shape) {} const osg::ref_ptr& getHolder() const { return mHolder; } const btCollisionShape& getShape() const { return mShape; } private: osg::ref_ptr mHolder; std::reference_wrapper mShape; }; class RecastMeshObject { public: RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType); const osg::ref_ptr& getHolder() const { return mHolder; } const btCollisionShape& getShape() const { return mShape; } const btTransform& getTransform() const { return mTransform; } AreaType getAreaType() const { return mAreaType; } private: osg::ref_ptr mHolder; std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; btVector3 mLocalScaling; std::vector mChildren; }; } #endif openmw-openmw-0.47.0/components/detournavigator/recastmeshtiles.hpp000066400000000000000000000005041413061077700257310ustar00rootroot00000000000000#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.47.0/components/detournavigator/recasttempallocator.hpp000066400000000000000000000035461413061077700266130ustar00rootroot00000000000000#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.47.0/components/detournavigator/settings.cpp000066400000000000000000000072541413061077700243760ustar00rootroot00000000000000#include "settings.hpp" #include namespace DetourNavigator { std::optional makeSettingsFromSettingsManager() { if (!::Settings::Manager::getBool("enable", "Navigator")) return std::optional(); Settings navigatorSettings; navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); navigatorSettings.mMaxClimb = 0; navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); navigatorSettings.mMaxSlope = 0; navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); navigatorSettings.mSwimHeightScale = 0; navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator"); navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(::Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); return navigatorSettings; } } openmw-openmw-0.47.0/components/detournavigator/settings.hpp000066400000000000000000000027111413061077700243740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #include #include #include namespace DetourNavigator { struct Settings { bool mEnableWriteRecastMeshToFile = false; bool mEnableWriteNavMeshToFile = false; bool mEnableRecastMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false; 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 mMaxNavMeshQueryNodes = 0; int mMaxPolys = 0; int mMaxTilesNumber = 0; int mMaxVertsPerPoly = 0; int mRegionMergeSize = 0; int mRegionMinSize = 0; int mTileSize = 0; int mWaitUntilMinDistanceToPlayer = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mMaxNavMeshTilesCacheSize = 0; std::size_t mMaxPolygonPathSize = 0; std::size_t mMaxSmoothPathSize = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; }; std::optional makeSettingsFromSettingsManager(); } #endif openmw-openmw-0.47.0/components/detournavigator/settingsutils.hpp000066400000000000000000000063261413061077700254630ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #include "settings.hpp" #include "tilebounds.hpp" #include "tileposition.hpp" #include "tilebounds.hpp" #include #include #include #include #include #include namespace DetourNavigator { inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents) { return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; } inline float getMaxClimb(const Settings& settings) { return settings.mMaxClimb * settings.mRecastScaleFactor; } inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents) { return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; } inline float toNavMeshCoordinates(const Settings& settings, float value) { return value * settings.mRecastScaleFactor; } inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position) { std::swap(position.y(), position.z()); return position * settings.mRecastScaleFactor; } inline osg::Vec3f fromNavMeshCoordinates(const Settings& 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 Settings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; } inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position) { return TilePosition( static_cast(std::floor(position.x() / getTileSize(settings))), static_cast(std::floor(position.z() / getTileSize(settings))) ); } inline TileBounds makeTileBounds(const Settings& 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 Settings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; } inline float getSwimLevel(const Settings& settings, const float agentHalfExtentsZ) { return - settings.mSwimHeightScale * agentHalfExtentsZ; } inline btTransform getSwimLevelTransform(const Settings& settings, const btTransform& transform, const float agentHalfExtentsZ) { return btTransform( transform.getBasis(), transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ) ); } inline float getRealTileSize(const Settings& settings) { return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; } inline float getMaxNavmeshAreaRadius(const Settings& settings) { return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } } #endif openmw-openmw-0.47.0/components/detournavigator/sharednavmesh.hpp000066400000000000000000000003631413061077700253650ustar00rootroot00000000000000#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.47.0/components/detournavigator/status.hpp000066400000000000000000000025501413061077700240600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H namespace DetourNavigator { enum class Status { Success, NavMeshNotFound, StartPolygonNotFound, EndPolygonNotFound, MoveAlongSurfaceFailed, FindPathOverPolygonsFailed, GetPolyHeightFailed, InitNavMeshQueryFailed, }; constexpr const char* getMessage(Status value) { switch (value) { case Status::Success: return "success"; 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.47.0/components/detournavigator/tilebounds.hpp000066400000000000000000000004001413061077700246750ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #include namespace DetourNavigator { struct TileBounds { osg::Vec2f mMin; osg::Vec2f mMax; }; } #endif openmw-openmw-0.47.0/components/detournavigator/tilecachedrecastmeshmanager.cpp000066400000000000000000000164721413061077700302370ustar00rootroot00000000000000#include "tilecachedrecastmeshmanager.hpp" #include "makenavmesh.hpp" #include "gettilespositions.hpp" #include "settingsutils.hpp" #include #include namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) : mSettings(settings) {} bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { std::vector tilesPositions; const auto border = getBorderSize(mSettings); { auto tiles = mTiles.lock(); getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) tilesPositions.push_back(tilePosition); }); } if (tilesPositions.empty()) return false; std::sort(tilesPositions.begin(), tilesPositions.end()); mObjectsTilesPositions.insert_or_assign(id, std::move(tilesPositions)); ++mRevision; return true; } std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) return std::nullopt; std::optional result; { auto tiles = mTiles.lock(); for (const auto& tilePosition : object->second) { const auto removed = removeTile(id, tilePosition, tiles.get()); if (removed && !result) result = removed; } } if (result) ++mRevision; return result; } bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { const auto border = getBorderSize(mSettings); auto& tilesPositions = mWaterTilesPositions[cellPosition]; bool result = false; if (cellSize == std::numeric_limits::max()) { const auto tiles = mTiles.lock(); for (auto& tile : *tiles) { if (tile.second->addWater(cellPosition, cellSize, transform)) { tilesPositions.push_back(tile.first); result = true; } } } else { getTilesPositions(cellSize, transform, mSettings, [&] (const TilePosition& tilePosition) { const auto tiles = mTiles.lock(); auto tile = tiles->find(tilePosition); if (tile == tiles->end()) { auto tileBounds = makeTileBounds(mSettings, tilePosition); tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles->insert(std::make_pair(tilePosition, std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } if (tile->second->addWater(cellPosition, cellSize, transform)) { 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 tiles = mTiles.lock(); const auto tile = tiles->find(tilePosition); if (tile == tiles->end()) continue; const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->isEmpty()) { tiles->erase(tile); ++mTilesGeneration; } if (tileResult && !result) result = tileResult; } if (result) ++mRevision; return result; } std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) { const auto manager = [&] () -> std::shared_ptr { const auto tiles = mTiles.lock(); const auto it = tiles->find(tilePosition); if (it == tiles->end()) return nullptr; return it->second; } (); if (manager == nullptr) return nullptr; return manager->getMesh(); } bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) { return mTiles.lockConst()->count(tilePosition); } std::size_t TileCachedRecastMeshManager::getRevision() const { return mRevision; } void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) { const auto tiles = mTiles.lock(); const auto it = tiles->find(tilePosition); if (it == tiles->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, float border, TilesMap& tiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) { auto tileBounds = makeTileBounds(mSettings, tilePosition); tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles.insert(std::make_pair( tilePosition, std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } 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(); const auto tileResult = tile->second->removeObject(id); if (tile->second->isEmpty()) { tiles.erase(tile); ++mTilesGeneration; } return tileResult; } } openmw-openmw-0.47.0/components/detournavigator/tilecachedrecastmeshmanager.hpp000066400000000000000000000110601413061077700302300ustar00rootroot00000000000000#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 #include #include #include #include namespace DetourNavigator { class TileCachedRecastMeshManager { public: TileCachedRecastMeshManager(const Settings& settings); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); template bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) return false; auto& currentTiles = object->second; const auto border = getBorderSize(mSettings); bool changed = false; std::vector newTiles; { auto tiles = mTiles.lock(); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) { newTiles.push_back(tilePosition); if (updateTile(id, transform, areaType, tilePosition, tiles.get())) { onChangedTile(tilePosition); changed = true; } } else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) { newTiles.push_back(tilePosition); onChangedTile(tilePosition); changed = true; } }; getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) { onChangedTile(tile); changed = true; } } } if (changed) { currentTiles = std::move(newTiles); ++mRevision; } return changed; } std::optional removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); std::optional removeWater(const osg::Vec2i& cellPosition); std::shared_ptr getMesh(const TilePosition& tilePosition); bool hasTile(const TilePosition& tilePosition); template void forEachTile(Function&& function) { for (auto& [tilePosition, recastMeshManager] : *mTiles.lock()) function(tilePosition, *recastMeshManager); } std::size_t getRevision() const; void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion); private: using TilesMap = std::map>; const Settings& mSettings; Misc::ScopeGuarded mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; 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, float border, 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); }; } #endif openmw-openmw-0.47.0/components/detournavigator/tileposition.hpp000066400000000000000000000003241413061077700252540ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H #include namespace DetourNavigator { using TilePosition = osg::Vec2i; } #endif openmw-openmw-0.47.0/components/detournavigator/version.hpp000066400000000000000000000007361413061077700242260ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H #include #include namespace DetourNavigator { struct Version { std::size_t mGeneration; std::size_t mRevision; friend inline bool operator<(const Version& lhs, const Version& rhs) { return std::tie(lhs.mGeneration, lhs.mRevision) < std::tie(rhs.mGeneration, rhs.mRevision); } }; } #endif openmw-openmw-0.47.0/components/detournavigator/waitconditiontype.hpp000066400000000000000000000004041413061077700263060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H namespace DetourNavigator { enum class WaitConditionType { requiredTilesPresent, allJobsDone, }; } #endif openmw-openmw-0.47.0/components/doc.hpp000066400000000000000000000012271413061077700200650ustar00rootroot00000000000000// 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.47.0/components/esm/000077500000000000000000000000001413061077700173715ustar00rootroot00000000000000openmw-openmw-0.47.0/components/esm/activespells.cpp000066400000000000000000000043671413061077700226050ustar00rootroot00000000000000#include "activespells.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void ActiveSpells::save(ESMWriter &esm) const { for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) { esm.writeHNString ("ID__", it->first); const ActiveSpellParams& params = it->second; esm.writeHNT ("CAST", params.mCasterActorId); esm.writeHNString ("DISP", params.mDisplayName); for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) { esm.writeHNT ("MGEF", effectIt->mEffectId); if (effectIt->mArg != -1) esm.writeHNT ("ARG_", effectIt->mArg); esm.writeHNT ("MAGN", effectIt->mMagnitude); esm.writeHNT ("DURA", effectIt->mDuration); esm.writeHNT ("EIND", effectIt->mEffectIndex); esm.writeHNT ("LEFT", effectIt->mTimeLeft); } } } void ActiveSpells::load(ESMReader &esm) { int format = esm.getFormat(); while (esm.isNextSub("ID__")) { std::string spellId = esm.getHString(); ActiveSpellParams params; esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); // 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"); 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"); params.mEffects.push_back(effect); } mSpells.insert(std::make_pair(spellId, params)); } } } openmw-openmw-0.47.0/components/esm/activespells.hpp000066400000000000000000000022001413061077700225720ustar00rootroot00000000000000#ifndef OPENMW_ESM_ACTIVESPELLS_H #define OPENMW_ESM_ACTIVESPELLS_H #include "effectlist.hpp" #include "defs.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. // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. struct ActiveEffect { int mEffectId; float mMagnitude; int mArg; // skill or attribute float mDuration; float mTimeLeft; int mEffectIndex; }; // format 0, saved games only struct ActiveSpells { struct ActiveSpellParams { std::vector mEffects; std::string mDisplayName; int mCasterActorId; }; typedef std::multimap TContainer; TContainer mSpells; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.47.0/components/esm/aipackage.cpp000066400000000000000000000046141413061077700220070ustar00rootroot00000000000000#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 char *name = (it->mType == AI_Escort) ? "AI_E" : "AI_F"; esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); esm.writeHNOCString("CNDT", it->mCellName); break; } default: break; } } } } openmw-openmw-0.47.0/components/esm/aipackage.hpp000066400000000000000000000041051413061077700220070ustar00rootroot00000000000000#ifndef OPENMW_ESM_AIPACKAGE_H #define OPENMW_ESM_AIPACKAGE_H #include #include #include "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; int mUnk; }; struct AITarget { float mX, mY, mZ; short mDuration; NAME32 mId; short mUnk; }; struct AIActivate { NAME32 mName; unsigned char mUnk; }; #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.47.0/components/esm/aisequence.cpp000066400000000000000000000150001413061077700222130ustar00rootroot00000000000000#include "aisequence.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #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"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("HIDD", mHidden); } 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"); } 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); } 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"); } 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); } void AiActivate::load(ESMReader &esm) { mTargetId = esm.getHNString("TARG"); } void AiActivate::save(ESMWriter &esm) const { esm.writeHNString("TARG", mTargetId); } 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); } AiSequence::~AiSequence() { for (std::vector::iterator it = mPackages.begin(); it != mPackages.end(); ++it) delete it->mPackage; } 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) { 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 (new AiWander()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Travel: { std::unique_ptr ptr (new AiTravel()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Escort: { std::unique_ptr ptr (new AiEscort()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Follow: { std::unique_ptr ptr (new AiFollow()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Activate: { std::unique_ptr ptr (new AiActivate()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Combat: { std::unique_ptr ptr (new AiCombat()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } case Ai_Pursue: { std::unique_ptr ptr (new AiPursue()); ptr->load(esm); mPackages.back().mPackage = ptr.release(); break; } default: return; } } esm.getHNOT (mLastAiPackage, "LAST"); } } } openmw-openmw-0.47.0/components/esm/aisequence.hpp000066400000000000000000000066261413061077700222360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_AISEQUENCE_H #define OPENMW_COMPONENTS_ESM_AISEQUENCE_H #include #include #include "defs.hpp" #include "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 = ESM::FourCC<'W','A','N','D'>::value, Ai_Travel = ESM::FourCC<'T','R','A','V'>::value, Ai_Escort = ESM::FourCC<'E','S','C','O'>::value, Ai_Follow = ESM::FourCC<'F','O','L','L'>::value, Ai_Activate = ESM::FourCC<'A','C','T','I'>::value, Ai_Combat = ESM::FourCC<'C','O','M','B'>::value, Ai_Pursue = ESM::FourCC<'P','U','R','S'>::value }; 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 ESM::TimeStamp mStartTime bool mStoredInitialActorPosition; ESM::Vector3 mInitialActorPosition; /// \todo add more AiWander state void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiTravel : AiPackage { AiTravelData mData; bool mHidden; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiEscort : AiPackage { AiEscortData mData; int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; 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; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiActivate : AiPackage { std::string mTargetId; 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; AiPackage* mPackage; }; struct AiSequence { AiSequence() { mLastAiPackage = -1; } ~AiSequence(); 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.47.0/components/esm/animationstate.cpp000066400000000000000000000026351413061077700231230ustar00rootroot00000000000000#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.47.0/components/esm/animationstate.hpp000066400000000000000000000013671413061077700231310ustar00rootroot00000000000000#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.47.0/components/esm/attr.cpp000066400000000000000000000025601413061077700210520ustar00rootroot00000000000000#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.47.0/components/esm/attr.hpp000066400000000000000000000013171413061077700210560ustar00rootroot00000000000000#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.47.0/components/esm/cellid.cpp000066400000000000000000000026021413061077700213310ustar00rootroot00000000000000#include "cellid.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" const std::string ESM::CellId::sDefaultWorldspace = "sys::default"; void ESM::CellId::load (ESMReader &esm) { mWorldspace = esm.getHNString ("SPAC"); if (esm.isNextSub ("CIDX")) { esm.getHT (mIndex, 8); mPaged = true; } else mPaged = false; } void ESM::CellId::save (ESMWriter &esm) const { esm.writeHNString ("SPAC", mWorldspace); if (mPaged) esm.writeHNT ("CIDX", mIndex, 8); } bool ESM::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 ESM::operator!= (const CellId& left, const CellId& right) { return !(left==right); } bool ESM::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.47.0/components/esm/cellid.hpp000066400000000000000000000012311413061077700213330ustar00rootroot00000000000000#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.47.0/components/esm/cellref.cpp000066400000000000000000000146031413061077700215150ustar00rootroot00000000000000#include "cellref.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { int GroundcoverIndex = std::numeric_limits::max(); } void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) esm.getHNT (*this, tag.c_str(), 8); else esm.getHNT (mIndex, tag.c_str()); } void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const { if (wide) esm.writeHNT (tag, *this, 8); else { int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff)<<24); esm.writeHNT (tag, refNum, 4); } } void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); loadData(esm, isDeleted); } void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) { // 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(); blank(); mRefNum.load (esm, wideRefNum); mRefID = esm.getHNOString ("NAME"); if (mRefID.empty()) { Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); } } void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::FourCC<'U','N','A','M'>::value: esm.getHT(mReferenceBlocked); break; case ESM::FourCC<'X','S','C','L'>::value: esm.getHT(mScale); if (mScale < 0.5) mScale = 0.5; else if (mScale > 2) mScale = 2; break; case ESM::FourCC<'A','N','A','M'>::value: mOwner = esm.getHString(); break; case ESM::FourCC<'B','N','A','M'>::value: mGlobalVariable = esm.getHString(); break; case ESM::FourCC<'X','S','O','L'>::value: mSoul = esm.getHString(); break; case ESM::FourCC<'C','N','A','M'>::value: mFaction = esm.getHString(); break; case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mFactionRank); break; case ESM::FourCC<'X','C','H','G'>::value: esm.getHT(mEnchantmentCharge); break; case ESM::FourCC<'I','N','T','V'>::value: esm.getHT(mChargeInt); break; case ESM::FourCC<'N','A','M','9'>::value: esm.getHT(mGoldValue); break; case ESM::FourCC<'D','O','D','T'>::value: esm.getHT(mDoorDest); mTeleport = true; break; case ESM::FourCC<'D','N','A','M'>::value: mDestCell = esm.getHString(); break; case ESM::FourCC<'F','L','T','V'>::value: esm.getHT(mLockLevel); break; case ESM::FourCC<'K','N','A','M'>::value: mKey = esm.getHString(); break; case ESM::FourCC<'T','N','A','M'>::value: mTrap = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mPos, 24); break; case ESM::FourCC<'N','A','M','0'>::value: esm.skipHSub(); break; case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.cacheSubName(); isLoaded = true; break; } } if (mLockLevel == 0 && !mKey.empty()) { mLockLevel = UnbreakableLock; mTrap.clear(); } } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); esm.writeHNCString("NAME", mRefID); if (isDeleted) { esm.writeHNCString("DELE", ""); return; } if (mScale != 1.0) { float scale = mScale; if (scale < 0.5) scale = 0.5; else if (scale > 2) scale = 2; esm.writeHNT("XSCL", scale); } 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 ESM::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; } } openmw-openmw-0.47.0/components/esm/cellref.hpp000066400000000000000000000114731413061077700215240ustar00rootroot00000000000000#ifndef OPENMW_ESM_CELLREF_H #define OPENMW_ESM_CELLREF_H #include #include #include "defs.hpp" namespace ESM { class ESMWriter; class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); extern int GroundcoverIndex; struct RefNum { unsigned int mIndex; int mContentFile; void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR"); void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; enum { RefNum_NoContentFile = -1 }; inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } // Note: this method should not be used for objects with invalid RefNum // (for example, for objects from disabled plugins in savegames). inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* 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(); }; 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 #include #include #include "statstate.hpp" #include "defs.hpp" #include "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::vector mSummonGraveyard; ESM::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; ESM::TimeStamp mTimeOfDeath; int mLevel; 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.47.0/components/esm/custommarkerstate.cpp000066400000000000000000000007541413061077700236600ustar00rootroot00000000000000#include "custommarkerstate.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void CustomMarker::save(ESM::ESMWriter &esm) const { esm.writeHNT("POSX", mWorldX); esm.writeHNT("POSY", mWorldY); mCell.save(esm); if (!mNote.empty()) esm.writeHNString("NOTE", mNote); } void CustomMarker::load(ESM::ESMReader &esm) { esm.getHNT(mWorldX, "POSX"); esm.getHNT(mWorldY, "POSY"); mCell.load(esm); mNote = esm.getHNOString("NOTE"); } } openmw-openmw-0.47.0/components/esm/custommarkerstate.hpp000066400000000000000000000010371413061077700236600ustar00rootroot00000000000000#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; ESM::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 (ESM::ESMReader& reader); void save (ESM::ESMWriter& writer) const; }; } #endif openmw-openmw-0.47.0/components/esm/debugprofile.cpp000066400000000000000000000027071413061077700225520ustar00rootroot00000000000000#include "debugprofile.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; case ESM::FourCC<'S','C','R','P'>::value: mScriptText = esm.getHString(); break; case ESM::FourCC<'F','L','A','G'>::value: esm.getHT(mFlags); break; case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); if (isDeleted) { esm.writeHNCString("DELE", ""); return; } esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); } void ESM::DebugProfile::blank() { mDescription.clear(); mScriptText.clear(); mFlags = 0; } openmw-openmw-0.47.0/components/esm/debugprofile.hpp000066400000000000000000000015261413061077700225550ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_DEBUGPROFILE_H #define COMPONENTS_ESM_DEBUGPROFILE_H #include namespace ESM { class ESMReader; class ESMWriter; struct DebugProfile { static unsigned int sRecordId; 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) }; 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.47.0/components/esm/defs.hpp000066400000000000000000000124271413061077700210310ustar00rootroot00000000000000#ifndef OPENMW_ESM_DEFS_H #define OPENMW_ESM_DEFS_H #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 }; #pragma pack(push) #pragma pack(1) // 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]); } }; #pragma pack(pop) 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 struct FourCC { static constexpr unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; }; enum RecNameInts { // format 0 / legacy REC_ACTI = FourCC<'A','C','T','I'>::value, REC_ALCH = FourCC<'A','L','C','H'>::value, REC_APPA = FourCC<'A','P','P','A'>::value, REC_ARMO = FourCC<'A','R','M','O'>::value, REC_BODY = FourCC<'B','O','D','Y'>::value, REC_BOOK = FourCC<'B','O','O','K'>::value, REC_BSGN = FourCC<'B','S','G','N'>::value, REC_CELL = FourCC<'C','E','L','L'>::value, REC_CLAS = FourCC<'C','L','A','S'>::value, REC_CLOT = FourCC<'C','L','O','T'>::value, REC_CNTC = FourCC<'C','N','T','C'>::value, REC_CONT = FourCC<'C','O','N','T'>::value, REC_CREA = FourCC<'C','R','E','A'>::value, REC_CREC = FourCC<'C','R','E','C'>::value, REC_DIAL = FourCC<'D','I','A','L'>::value, REC_DOOR = FourCC<'D','O','O','R'>::value, REC_ENCH = FourCC<'E','N','C','H'>::value, REC_FACT = FourCC<'F','A','C','T'>::value, REC_GLOB = FourCC<'G','L','O','B'>::value, REC_GMST = FourCC<'G','M','S','T'>::value, REC_INFO = FourCC<'I','N','F','O'>::value, REC_INGR = FourCC<'I','N','G','R'>::value, REC_LAND = FourCC<'L','A','N','D'>::value, REC_LEVC = FourCC<'L','E','V','C'>::value, REC_LEVI = FourCC<'L','E','V','I'>::value, REC_LIGH = FourCC<'L','I','G','H'>::value, REC_LOCK = FourCC<'L','O','C','K'>::value, REC_LTEX = FourCC<'L','T','E','X'>::value, REC_MGEF = FourCC<'M','G','E','F'>::value, REC_MISC = FourCC<'M','I','S','C'>::value, REC_NPC_ = FourCC<'N','P','C','_'>::value, REC_NPCC = FourCC<'N','P','C','C'>::value, REC_PGRD = FourCC<'P','G','R','D'>::value, REC_PROB = FourCC<'P','R','O','B'>::value, REC_RACE = FourCC<'R','A','C','E'>::value, REC_REGN = FourCC<'R','E','G','N'>::value, REC_REPA = FourCC<'R','E','P','A'>::value, REC_SCPT = FourCC<'S','C','P','T'>::value, REC_SKIL = FourCC<'S','K','I','L'>::value, REC_SNDG = FourCC<'S','N','D','G'>::value, REC_SOUN = FourCC<'S','O','U','N'>::value, REC_SPEL = FourCC<'S','P','E','L'>::value, REC_SSCR = FourCC<'S','S','C','R'>::value, REC_STAT = FourCC<'S','T','A','T'>::value, REC_WEAP = FourCC<'W','E','A','P'>::value, // format 0 - saved games REC_SAVE = FourCC<'S','A','V','E'>::value, REC_JOUR_LEGACY = FourCC<0xa4,'U','O','R'>::value, // "\xa4UOR", rather than "JOUR", little oversight when magic numbers were // calculated by hand, needs to be supported for older files now REC_JOUR = FourCC<'J','O','U','R'>::value, REC_QUES = FourCC<'Q','U','E','S'>::value, REC_GSCR = FourCC<'G','S','C','R'>::value, REC_PLAY = FourCC<'P','L','A','Y'>::value, REC_CSTA = FourCC<'C','S','T','A'>::value, REC_GMAP = FourCC<'G','M','A','P'>::value, REC_DIAS = FourCC<'D','I','A','S'>::value, REC_WTHR = FourCC<'W','T','H','R'>::value, REC_KEYS = FourCC<'K','E','Y','S'>::value, REC_DYNA = FourCC<'D','Y','N','A'>::value, REC_ASPL = FourCC<'A','S','P','L'>::value, REC_ACTC = FourCC<'A','C','T','C'>::value, REC_MPRJ = FourCC<'M','P','R','J'>::value, REC_PROJ = FourCC<'P','R','O','J'>::value, REC_DCOU = FourCC<'D','C','O','U'>::value, REC_MARK = FourCC<'M','A','R','K'>::value, REC_ENAB = FourCC<'E','N','A','B'>::value, REC_CAM_ = FourCC<'C','A','M','_'>::value, REC_STLN = FourCC<'S','T','L','N'>::value, REC_INPU = FourCC<'I','N','P','U'>::value, // format 1 REC_FILT = FourCC<'F','I','L','T'>::value, REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; /// Common subrecords enum SubRecNameInts { SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, SREC_NAME = ESM::FourCC<'N','A','M','E'>::value }; } #endif openmw-openmw-0.47.0/components/esm/dialoguestate.cpp000066400000000000000000000027131413061077700227320ustar00rootroot00000000000000#include "dialoguestate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::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 ESM::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.47.0/components/esm/dialoguestate.hpp000066400000000000000000000010561413061077700227360ustar00rootroot00000000000000#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.47.0/components/esm/doorstate.cpp000066400000000000000000000015501413061077700221020ustar00rootroot00000000000000#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.47.0/components/esm/doorstate.hpp000066400000000000000000000010511413061077700221030ustar00rootroot00000000000000#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.47.0/components/esm/effectlist.cpp000066400000000000000000000010401413061077700222200ustar00rootroot00000000000000#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.getHT(s, 24); 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.47.0/components/esm/effectlist.hpp000066400000000000000000000020561413061077700222350ustar00rootroot00000000000000#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.47.0/components/esm/esmcommon.hpp000066400000000000000000000104771413061077700221100ustar00rootroot00000000000000#ifndef OPENMW_ESM_COMMON_H #define OPENMW_ESM_COMMON_H #include #include #include #include #include namespace ESM { enum Version { VER_12 = 0x3f99999a, VER_13 = 0x3fa66666 }; // CRTP for FIXED_STRING class, a structure used for holding fixed-length strings template< template class DERIVED, size_t SIZE> class FIXED_STRING_BASE { /* The following methods must be implemented in derived classes: * char const* ro_data() const; // return pointer to ro buffer * char* rw_data(); // return pointer to rw buffer */ public: enum { size = SIZE }; template bool operator==(char const (&str)[OTHER_SIZE]) const { size_t other_len = strnlen(str, OTHER_SIZE); if (other_len != this->length()) return false; return std::strncmp(self()->ro_data(), str, size) == 0; } //this operator will not be used for char[N], only for char* template::value>::type> bool operator==(const T* const& str) const { char const* const data = self()->ro_data(); for(size_t i = 0; i < size; ++i) { if(data[i] != str[i]) return false; else if(data[i] == '\0') return true; } return str[size] == '\0'; } bool operator!=(const char* const str) const { return !( (*this) == str ); } bool operator==(const std::string& str) const { return (*this) == str.c_str(); } bool operator!=(const std::string& str) const { return !( (*this) == str ); } static size_t data_size() { return size; } size_t length() const { return strnlen(self()->ro_data(), size); } std::string toString() const { return std::string(self()->ro_data(), this->length()); } void assign(const std::string& value) { std::strncpy(self()->rw_data(), value.c_str(), size-1); self()->rw_data()[size-1] = '\0'; } void clear() { this->assign(""); } private: DERIVED const* self() const { return static_cast const*>(this); } // write the non-const version in terms of the const version // Effective C++ 3rd ed., Item 3 (p. 24-25) DERIVED* self() { return const_cast*>(static_cast(this)->self()); } }; // Generic implementation template struct FIXED_STRING : public FIXED_STRING_BASE { char data[SIZE]; char const* ro_data() const { return data; } char* rw_data() { return data; } }; // In the case of SIZE=4, it can be more efficient to match the string // as a 32 bit number, therefore the struct is implemented as a union with an int. template <> struct FIXED_STRING<4> : public FIXED_STRING_BASE { union { char data[4]; uint32_t intval; }; using FIXED_STRING_BASE::operator==; using FIXED_STRING_BASE::operator!=; bool operator==(uint32_t v) const { return v == intval; } bool operator!=(uint32_t v) const { return v != intval; } void assign(const std::string& value) { intval = 0; std::memcpy(data, value.data(), std::min(value.size(), sizeof(data))); } char const* ro_data() const { return data; } char* rw_data() { return data; } }; typedef FIXED_STRING<4> NAME; typedef FIXED_STRING<32> NAME32; typedef FIXED_STRING<64> NAME64; /* 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.47.0/components/esm/esmreader.cpp000066400000000000000000000175611413061077700220560ustar00rootroot00000000000000#include "esmreader.hpp" #include namespace ESM { using namespace Misc; std::string ESMReader::getName() const { return mCtx.filename; } ESM_Context ESMReader::getContext() { // Update the file position before returning mCtx.filePos = mEsm->tellg(); return mCtx; } ESMReader::ESMReader() : mRecordFlags(0) , mBuffer(50*1024) , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) { clearCtx(); } int ESMReader::getFormat() const { return mHeader.mFormat; } 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(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) { close(); mEsm = _esm; mCtx.filename = name; mEsm->seekg(0, mEsm->end); mCtx.leftFile = mFileSize = mEsm->tellg(); mEsm->seekg(0, mEsm->beg); } void ESMReader::openRaw(const std::string& filename) { openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { openRaw(_esm, name); if (getRecName() != "TES3") fail("Not a valid Morrowind file"); getRecHeader(); mHeader.load (*this); } void ESMReader::open(const std::string &file) { open (Files::openConstrainedFileStream (file.c_str ()), file); } std::string ESMReader::getHNOString(const char* name) { if (isNextSub(name)) return getHString(); return ""; } std::string ESMReader::getHNString(const char* 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 && !mEsm->peek()) { // Skip the following zero byte mCtx.leftRec--; char c; getExact(&c, 1); return ""; } return getString(mCtx.leftSub); } void ESMReader::getHExact(void*p, int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) { std::stringstream error; error << "getHExact(): size mismatch (requested " << size << ", got " << mCtx.leftSub << ")"; fail(error.str()); } getExact(p, size); } // Read the given number of bytes from a named subrecord void ESMReader::getHNExact(void*p, int size, const char* name) { getSubNameIs(name); getHExact(p, size); } // Get the next subrecord name and check if it matches the parameter void ESMReader::getSubNameIs(const char* name) { getSubName(); if (mCtx.subName != name) fail( "Expected subrecord " + std::string(name) + " but got " + mCtx.subName.toString()); } bool ESMReader::isNextSub(const char* name) { if (!mCtx.leftRec) 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(const char *name) { if (!mCtx.leftRec) return false; getSubName(); mCtx.subCached = true; return mCtx.subName == name; } void ESMReader::cacheSubName() { mCtx.subCached = true; } // 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 int subNameSize = static_cast(mCtx.subName.data_size()); getExact(mCtx.subName.rw_data(), 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) fail("skipHSubSize() mismatch"); } void ESMReader::skipHSubUntil(const char *name) { while (hasMoreSubs() && !isNextSub(name)) { mCtx.subCached = false; skipHSub(); } if (hasMoreSubs()) mCtx.subCached = true; } void ESMReader::getSubHeader() { if (mCtx.leftRec < 4) fail("End of record while reading sub-record header"); // Get subrecord size getT(mCtx.leftSub); // Adjust number of record bytes left mCtx.leftRec -= mCtx.leftSub + 4; } void ESMReader::getSubHeaderIs(int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) fail("getSubHeaderIs(): Sub header mismatch"); } NAME ESMReader::getRecName() { if (!hasMoreRecs()) fail("No more records, getRecName() failed"); getName(mCtx.recName); mCtx.leftFile -= mCtx.recName.data_size(); // 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 < 12) 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 -= 12; // Check that sizes add up if (mCtx.leftFile < mCtx.leftRec) fail("Record size is larger than rest of file"); // Adjust number of bytes mCtx.left in file mCtx.leftFile -= mCtx.leftRec; } /************************************************************************* * * Lowest level data reading and misc methods * *************************************************************************/ void ESMReader::getExact(void*x, int size) { try { mEsm->read((char*)x, size); } catch (std::exception& e) { fail(std::string("Read error: ") + e.what()); } } 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 mEncoder->getUtf8(ptr, size); return std::string (ptr, size); } void ESMReader::fail(const std::string &msg) { std::stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); if (mEsm.get()) ss << "\n Offset: 0x" << std::hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } size_t ESMReader::getFileOffset() const { return mEsm->tellg(); } void ESMReader::skip(int bytes) { mEsm->seekg(getFileOffset()+bytes); } } openmw-openmw-0.47.0/components/esm/esmreader.hpp000066400000000000000000000205111413061077700220500ustar00rootroot00000000000000#ifndef OPENMW_ESM_READER_H #define OPENMW_ESM_READER_H #include #include #include #include #include #include #include #include "esmcommon.hpp" #include "loadtes3.hpp" namespace ESM { 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; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } std::string getName() const; /************************************************************************* * * 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(Files::IStreamPtr _esm, const std::string &name); /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. void open(Files::IStreamPtr _esm, const std::string &name); void open(const std::string &file); void openRaw(const std::string &filename); /// Get the current position in the file. Make sure that the file has been opened! size_t getFileOffset() const; // 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() {return mCtx.index;} void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} std::vector *getGlobalReaderList() {return mGlobalReaderList;} void addParentFileIndex(int index) { mCtx.parentFileIndices.push_back(index); } 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, const char* name) { getSubNameIs(name); getHT(x); } // Optional version of getHNT template void getHNOT(X &x, const char* 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 getHNT(X &x, const char* name, int size) { assert(sizeof(X) == size); getSubNameIs(name); getHT(x); } template void getHNOT(X &x, const char* name, int size) { assert(sizeof(X) == size); if(isNextSub(name)) getHT(x); } // Get data of a given type/size, including subrecord header template void getHT(X &x) { getSubHeader(); if (mCtx.leftSub != sizeof(X)) { std::stringstream error; error << "getHT(): subrecord size mismatch (requested " << sizeof(X) << ", got " << mCtx.leftSub << ")"; fail(error.str()); } getT(x); } // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template void getHT(X &x, int size) { assert(sizeof(X) == size); getHT(x); } // Read a string by the given name if it is the next record. std::string getHNOString(const char* name); // Read a string with the given sub-record name std::string getHNString(const char* name); // Read a string, including the sub-record header (but not the name) std::string getHString(); // 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, const char* name); /************************************************************************* * * Low level sub-record methods * *************************************************************************/ // Get the next subrecord name and check if it matches the parameter void getSubNameIs(const char* 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(const char* name); bool peekNextSub(const char* name); // Store the current subrecord name for the next call of getSubName() void cacheSubName(); // 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(const char* name); /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. */ void getSubHeader(); /** Get sub header and check the size */ void getSubHeaderIs(int size); /************************************************************************* * * 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)); } void getExact(void*x, int 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(int bytes); /// Used for error handling void fail(const std::string &msg); /// Sets font encoder for ESM strings void setEncoder(ToUTF8::Utf8Encoder* encoder); /// Get record flags of last record unsigned int getRecordFlags() { return mRecordFlags; } size_t getFileSize() const { return mFileSize; } private: void clearCtx(); Files::IStreamPtr mEsm; ESM_Context mCtx; unsigned int mRecordFlags; // Special file signifier (see SpecialFile enum above) // Buffer for ESM strings std::vector mBuffer; Header mHeader; std::vector *mGlobalReaderList; ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; }; } #endif openmw-openmw-0.47.0/components/esm/esmwriter.cpp000066400000000000000000000126161413061077700221240ustar00rootroot00000000000000#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(const std::string& 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) { std::string type; for (int i=0; i<4; ++i) /// \todo make endianess agnostic type += reinterpret_cast (&name)[i]; startRecord (type, flags); } void ESMWriter::startSubRecord(const std::string& 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(const std::string& 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) { std::string type; for (int i=0; i<4; ++i) /// \todo make endianess agnostic type += reinterpret_cast (&name)[i]; endRecord (type); } void ESMWriter::writeHNString(const std::string& name, const std::string& data) { startSubRecord(name); writeHString(data); endRecord(name); } void ESMWriter::writeHNString(const std::string& 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 std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; write(string.c_str(), 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(const std::string& name) { assert((name.size() == 4 && name[3] != '\0')); write(name.c_str(), name.size()); } 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.47.0/components/esm/esmwriter.hpp000066400000000000000000000113121413061077700221210ustar00rootroot00000000000000#ifndef OPENMW_ESM_WRITER_H #define OPENMW_ESM_WRITER_H #include #include #include "esmcommon.hpp" #include "loadtes3.hpp" namespace ToUTF8 { class Utf8Encoder; } namespace ESM { class ESMWriter { struct RecordData { std::string name; std::streampos position; uint32_t size; }; public: ESMWriter(); unsigned int getVersion() const; // Set various header data (ESM::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); // 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(const std::string& name, const std::string& data); void writeHNString(const std::string& name, const std::string& data, size_t size); void writeHNCString(const std::string& name, const std::string& data) { startSubRecord(name); writeHCString(data); endRecord(name); } void writeHNOString(const std::string& name, const std::string& data) { if (!data.empty()) writeHNString(name, data); } void writeHNOCString(const std::string& name, const std::string& data) { if (!data.empty()) writeHNCString(name, data); } template void writeHNT(const std::string& name, const T& data) { startSubRecord(name); writeT(data); endRecord(name); } template void writeHNT(const std::string& 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(const std::string& name, const std::string& data) = delete; void writeT(const std::string& data) = delete; template void writeHNT(const std::string& name, const T (&data)[size], int) = delete; template void writeHNT(const std::string& name, const T& data, int size) { startSubRecord(name); writeT(data, size); endRecord(name); } template void writeT(const T& data) { write((char*)&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) { write((char*)&data, size); } void startRecord(const std::string& 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(const std::string& name); void endRecord(const std::string& 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(const std::string& 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.47.0/components/esm/filter.cpp000066400000000000000000000024301413061077700213610ustar00rootroot00000000000000#include "filter.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().intval; switch (name) { case ESM::SREC_NAME: mId = esm.getHString(); break; case ESM::FourCC<'F','I','L','T'>::value: mFilter = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); if (isDeleted) { esm.writeHNCString("DELE", ""); return; } esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } void ESM::Filter::blank() { mFilter.clear(); mDescription.clear(); } openmw-openmw-0.47.0/components/esm/filter.hpp000066400000000000000000000010131413061077700213620ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_FILTER_H #define COMPONENTS_ESM_FILTER_H #include namespace ESM { class ESMReader; class ESMWriter; struct Filter { static unsigned int sRecordId; 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.47.0/components/esm/fogstate.cpp000066400000000000000000000050101413061077700217050ustar00rootroot00000000000000#include "fogstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include #include #include #include "savedgame.hpp" 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 ESM::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 ESM::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.47.0/components/esm/fogstate.hpp000066400000000000000000000013371413061077700217220ustar00rootroot00000000000000#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.47.0/components/esm/globalmap.cpp000066400000000000000000000017561413061077700220440ustar00rootroot00000000000000#include "globalmap.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; void ESM::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 ESM::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.47.0/components/esm/globalmap.hpp000066400000000000000000000013361413061077700220430ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H #define OPENMW_COMPONENTS_ESM_GLOBALMAP_H #include #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only ///< \brief An image containing the explored areas on the global map. struct GlobalMap { static unsigned int sRecordId; // 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.47.0/components/esm/globalscript.cpp000066400000000000000000000013551413061077700225660ustar00rootroot00000000000000#include "globalscript.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::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 ESM::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.hasContentFile()) mTargetRef.save (esm, true, "FRMR"); } } openmw-openmw-0.47.0/components/esm/globalscript.hpp000066400000000000000000000010661413061077700225720ustar00rootroot00000000000000#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.47.0/components/esm/inventorystate.cpp000066400000000000000000000110641413061077700231750ustar00rootroot00000000000000#include "inventorystate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include void ESM::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 ESM::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.47.0/components/esm/inventorystate.hpp000066400000000000000000000016351413061077700232050ustar00rootroot00000000000000#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.47.0/components/esm/journalentry.cpp000066400000000000000000000017041413061077700226330ustar00rootroot00000000000000#include "journalentry.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::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 ESM::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.47.0/components/esm/journalentry.hpp000066400000000000000000000013501413061077700226350ustar00rootroot00000000000000#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.47.0/components/esm/loadacti.cpp000066400000000000000000000033561413061077700216640ustar00rootroot00000000000000#include "loadacti.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; void Activator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); } void Activator::blank() { mName.clear(); mScript.clear(); mModel.clear(); } } openmw-openmw-0.47.0/components/esm/loadacti.hpp000066400000000000000000000011241413061077700216600ustar00rootroot00000000000000#ifndef OPENMW_ESM_ACTI_H #define OPENMW_ESM_ACTI_H #include namespace ESM { class ESMReader; class ESMWriter; struct Activator { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Activator"; } 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.47.0/components/esm/loadalch.cpp000066400000000000000000000050621413061077700216470ustar00rootroot00000000000000#include "loadalch.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; void Potion::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'T','E','X','T'>::value: // not ITEX here for some reason mIcon = esm.getHString(); break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'A','L','D','T'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mWeight = 0; mData.mValue = 0; mData.mAutoCalc = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); mEffects.mList.clear(); } } openmw-openmw-0.47.0/components/esm/loadalch.hpp000066400000000000000000000014541413061077700216550ustar00rootroot00000000000000#ifndef OPENMW_ESM_ALCH_H #define OPENMW_ESM_ALCH_H #include #include "effectlist.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Alchemy item (potions) */ struct Potion { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Potion"; } struct ALDTstruct { float mWeight; int mValue; int mAutoCalc; }; ALDTstruct mData; 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.47.0/components/esm/loadappa.cpp000066400000000000000000000045271413061077700216660ustar00rootroot00000000000000#include "loadappa.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; void Apparatus::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'A','A','D','T'>::value: esm.getHT(mData); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mType = 0; mData.mQuality = 0; mData.mWeight = 0; mData.mValue = 0; mModel.clear(); mIcon.clear(); mScript.clear(); mName.clear(); } } openmw-openmw-0.47.0/components/esm/loadappa.hpp000066400000000000000000000016031413061077700216630ustar00rootroot00000000000000#ifndef OPENMW_ESM_APPA_H #define OPENMW_ESM_APPA_H #include namespace ESM { class ESMReader; class ESMWriter; /* * Alchemist apparatus */ struct Apparatus { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadarmo.cpp000066400000000000000000000070371413061077700217020ustar00rootroot00000000000000#include "loadarmo.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.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); } } unsigned int Armor::sRecordId = REC_ARMO; void Armor::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mParts.mParts.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'A','O','D','T'>::value: esm.getHT(mData, 24); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadarmo.hpp000066400000000000000000000042201413061077700216760ustar00rootroot00000000000000#ifndef OPENMW_ESM_ARMO_H #define OPENMW_ESM_ARMO_H #include #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadbody.cpp000066400000000000000000000037041413061077700216760ustar00rootroot00000000000000#include "loadbody.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; void BodyPart::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mRace = esm.getHString(); break; case ESM::FourCC<'B','Y','D','T'>::value: esm.getHT(mData, 4); hasData = true; break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mRace); esm.writeHNT("BYDT", mData, 4); } void BodyPart::blank() { mData.mPart = 0; mData.mVampire = 0; mData.mFlags = 0; mData.mType = 0; mModel.clear(); mRace.clear(); } } openmw-openmw-0.47.0/components/esm/loadbody.hpp000066400000000000000000000025351413061077700217040ustar00rootroot00000000000000#ifndef OPENMW_ESM_BODY_H #define OPENMW_ESM_BODY_H #include namespace ESM { class ESMReader; class ESMWriter; struct BodyPart { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadbook.cpp000066400000000000000000000053741413061077700217000ustar00rootroot00000000000000#include "loadbook.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Book::sRecordId = REC_BOOK; void Book::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'B','K','D','T'>::value: esm.getHT(mData, 20); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadbook.hpp000066400000000000000000000014451413061077700217000ustar00rootroot00000000000000#ifndef OPENMW_ESM_BOOK_H #define OPENMW_ESM_BOOK_H #include namespace ESM { /* * Books, magic scrolls, notes and so on */ class ESMReader; class ESMWriter; struct Book { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Book"; } struct BKDTstruct { float mWeight; int mValue, mIsScroll, mSkillId, mEnchant; }; BKDTstruct mData; std::string mName, mModel, mIcon, mScript, mEnchant, mText; 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.47.0/components/esm/loadbsgn.cpp000066400000000000000000000037331413061077700216740ustar00rootroot00000000000000#include "loadbsgn.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; void BirthSign::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mPowers.mList.clear(); bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'T','N','A','M'>::value: mTexture = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); mPowers.save(esm); } void BirthSign::blank() { mName.clear(); mDescription.clear(); mTexture.clear(); mPowers.mList.clear(); } } openmw-openmw-0.47.0/components/esm/loadbsgn.hpp000066400000000000000000000013261413061077700216750ustar00rootroot00000000000000#ifndef OPENMW_ESM_BSGN_H #define OPENMW_ESM_BSGN_H #include #include "spelllist.hpp" namespace ESM { class ESMReader; class ESMWriter; struct BirthSign { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "BirthSign"; } 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.47.0/components/esm/loadcell.cpp000066400000000000000000000206531413061077700216620ustar00rootroot00000000000000#include "loadcell.hpp" #include #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" #include "cellid.hpp" namespace { ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum void adjustRefNum (ESM::RefNum& refNum, ESM::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 { unsigned int Cell::sRecordId = REC_CELL; // 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().intval) { case ESM::SREC_NAME: mName = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::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 = ESM::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 isLoaded = false; mHasAmbi = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','T','V'>::value: int waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; break; case ESM::FourCC<'W','H','G','T'>::value: esm.getHT(mWater); mWaterInt = false; break; case ESM::FourCC<'A','M','B','I'>::value: esm.getHT(mAmbi); mHasAmbi = true; break; case ESM::FourCC<'R','G','N','N'>::value: mRegion = esm.getHString(); break; case ESM::FourCC<'N','A','M','5'>::value: esm.getHT(mMapColor); break; case ESM::FourCC<'N','A','M','0'>::value: 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.writeHNCString("DELE", ""); 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); } if (mRefNumCounter != 0) esm.writeHNT("NAM0", mRefNumCounter); } 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, bool ignoreMoves, MovedCellRef *mref) { isDeleted = false; // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) return false; // NOTE: We should not need this check. It is a safety check until we have checked // more plugins, and how they treat these moved references. if (esm.isNextSub("MVRF")) { if (ignoreMoves) { esm.getHT (mref->mRefNum.mIndex); esm.getHNOT (mref->mTarget, "CNDT"); adjustRefNum (mref->mRefNum, esm); } else { // skip rest of cell record (moved references), they are handled elsewhere esm.skipRecord(); // skip MVRF, CNDT return false; } } if (esm.peekNextSub("FRMR")) { ref.load (esm, isDeleted); // Identify references belonging to a parent file and adapt the ID accordingly. adjustRefNum (ref.mRefNum, esm); return true; } return false; } 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.47.0/components/esm/loadcell.hpp000066400000000000000000000140431413061077700216630ustar00rootroot00000000000000#ifndef OPENMW_ESM_CELL_H #define OPENMW_ESM_CELL_H #include #include #include #include "esmcommon.hpp" #include "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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Cell"; } 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; 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. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. static bool getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves = false, MovedCellRef *mref = nullptr); /* 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.47.0/components/esm/loadclas.cpp000066400000000000000000000056771413061077700216760ustar00rootroot00000000000000#include "loadclas.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Class::sRecordId = REC_CLAS; 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; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'C','L','D','T'>::value: esm.getHT(mData, 60); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); } void Class::blank() { 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.47.0/components/esm/loadclas.hpp000066400000000000000000000040111413061077700216600ustar00rootroot00000000000000#ifndef OPENMW_ESM_CLAS_H #define OPENMW_ESM_CLAS_H #include namespace ESM { class ESMReader; class ESMWriter; /* * Character class definitions */ // These flags tells us which items should be auto-calculated for this // class struct Class { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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 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.47.0/components/esm/loadclot.cpp000066400000000000000000000053761413061077700217110ustar00rootroot00000000000000#include "loadclot.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; void Clothing::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mParts.mParts.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'C','T','D','T'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadclot.hpp000066400000000000000000000020531413061077700217030ustar00rootroot00000000000000#ifndef OPENMW_ESM_CLOT_H #define OPENMW_ESM_CLOT_H #include #include "loadarmo.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Clothing */ struct Clothing { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadcont.cpp000066400000000000000000000065761413061077700217160ustar00rootroot00000000000000#include "loadcont.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "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"); } } unsigned int Container::sRecordId = REC_CONT; void Container::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mInventory.mList.clear(); bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'C','N','D','T'>::value: esm.getHT(mWeight, 4); hasWeight = true; break; case ESM::FourCC<'F','L','A','G'>::value: esm.getHT(mFlags, 4); if (mFlags & 0xf4) esm.fail("Unknown flags"); if (!(mFlags & 0x8)) esm.fail("Flag 8 not set"); hasFlags = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; case ESM::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.writeHNCString("DELE", ""); 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() { mName.clear(); mModel.clear(); mScript.clear(); mWeight = 0; mFlags = 0x8; // set default flag value mInventory.mList.clear(); } } openmw-openmw-0.47.0/components/esm/loadcont.hpp000066400000000000000000000023231413061077700217050ustar00rootroot00000000000000#ifndef OPENMW_ESM_CONT_H #define OPENMW_ESM_CONT_H #include #include #include "esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Container definition */ struct ContItem { int mCount; 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Container"; } enum Flags { Organic = 1, // Objects cannot be placed in this container Respawn = 2, // Respawns after 4 months Unknown = 8 }; 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.47.0/components/esm/loadcrea.cpp000066400000000000000000000125601413061077700216530ustar00rootroot00000000000000#include "loadcrea.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Creature::sRecordId = REC_CREA; void Creature::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mPersistent = (esm.getRecordFlags() & 0x0400) != 0; 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().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'C','N','A','M'>::value: mOriginal = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'N','P','D','T'>::value: esm.getHT(mData, 96); hasNpdt = true; break; case ESM::FourCC<'F','L','A','G'>::value: int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; case ESM::FourCC<'X','S','C','L'>::value: esm.getHT(mScale); break; case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; case ESM::FourCC<'N','P','C','S'>::value: mSpells.add(esm); break; case ESM::FourCC<'A','I','D','T'>::value: esm.getHExact(&mAiData, sizeof(mAiData)); break; case ESM::FourCC<'D','O','D','T'>::value: case ESM::FourCC<'D','N','A','M'>::value: 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 ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; case ESM::FourCC<'I','N','D','X'>::value: // 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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadcrea.hpp000066400000000000000000000047521413061077700216640ustar00rootroot00000000000000#ifndef OPENMW_ESM_CREA_H #define OPENMW_ESM_CREA_H #include #include "loadcont.hpp" #include "spelllist.hpp" #include "aipackage.hpp" #include "transport.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Creature definition * */ struct Creature { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; bool mPersistent; float mScale; 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.47.0/components/esm/loaddial.cpp000066400000000000000000000073021413061077700216500ustar00rootroot00000000000000#include "loaddial.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; 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().intval) { case ESM::FourCC<'D','A','T','A'>::value: { esm.getSubHeader(); int size = esm.getSubSize(); if (size == 1) { esm.getT(mType); } else { esm.skip(size); } break; } case ESM::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.writeHNCString("DELE", ""); } else { esm.writeHNT("DATA", mType); } } void Dialogue::blank() { mInfo.clear(); } void Dialogue::readInfo(ESMReader &esm, bool merge) { ESM::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; } InfoContainer::iterator it = mInfo.end(); LookupMap::iterator lookup; lookup = mLookup.find(info.mId); if (lookup != mLookup.end()) { it = lookup->second.first; // Since the new version of this record may have changed the next/prev linked list connection, we need to re-insert the record mInfo.erase(it); mLookup.erase(lookup); } if (info.mNext.empty()) { mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); return; } if (info.mPrev.empty()) { mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); return; } lookup = mLookup.find(info.mPrev); if (lookup != mLookup.end()) { it = lookup->second.first; mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); return; } lookup = mLookup.find(info.mNext); if (lookup != mLookup.end()) { it = lookup->second.first; mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); return; } Log(Debug::Warning) << "Warning: Failed to insert info " << info.mId; } 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.47.0/components/esm/loaddial.hpp000066400000000000000000000034361413061077700216610ustar00rootroot00000000000000#ifndef OPENMW_ESM_DIAL_H #define OPENMW_ESM_DIAL_H #include #include #include #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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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 (ESM::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.47.0/components/esm/loaddoor.cpp000066400000000000000000000042101413061077700216750ustar00rootroot00000000000000#include "loaddoor.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Door::sRecordId = REC_DOOR; void Door::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'S','N','A','M'>::value: mOpenSound = esm.getHString(); break; case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SNAM", mOpenSound); esm.writeHNOCString("ANAM", mCloseSound); } void Door::blank() { mName.clear(); mModel.clear(); mScript.clear(); mOpenSound.clear(); mCloseSound.clear(); } } openmw-openmw-0.47.0/components/esm/loaddoor.hpp000066400000000000000000000011421413061077700217030ustar00rootroot00000000000000#ifndef OPENMW_ESM_DOOR_H #define OPENMW_ESM_DOOR_H #include namespace ESM { class ESMReader; class ESMWriter; struct Door { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Door"; } 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.47.0/components/esm/loadench.cpp000066400000000000000000000034331413061077700216550ustar00rootroot00000000000000#include "loadench.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; void Enchantment::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'E','N','D','T'>::value: esm.getHT(mData, 16); hasData = true; break; case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNT("ENDT", mData, 16); mEffects.save(esm); } void Enchantment::blank() { mData.mType = 0; mData.mCost = 0; mData.mCharge = 0; mData.mFlags = 0; mEffects.mList.clear(); } } openmw-openmw-0.47.0/components/esm/loadench.hpp000066400000000000000000000017051413061077700216620ustar00rootroot00000000000000#ifndef OPENMW_ESM_ENCH_H #define OPENMW_ESM_ENCH_H #include #include "effectlist.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Enchantments */ struct Enchantment { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; }; 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.47.0/components/esm/loadfact.cpp000066400000000000000000000073131413061077700216560ustar00rootroot00000000000000#include "loadfact.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Faction::sRecordId = REC_FACT; 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; 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().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'R','N','A','M'>::value: if (rankCounter >= 10) esm.fail("Rank out of range"); mRanks[rankCounter++] = esm.getHString(); break; case ESM::FourCC<'F','A','D','T'>::value: esm.getHT(mData, 240); if (mData.mIsHidden > 1) esm.fail("Unknown flag!"); hasData = true; break; case ESM::FourCC<'A','N','A','M'>::value: { std::string faction = esm.getHString(); int reaction; esm.getHNT(reaction, "INTV"); mReactions[faction] = reaction; break; } case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadfact.hpp000066400000000000000000000034141413061077700216610ustar00rootroot00000000000000#ifndef OPENMW_ESM_FACT_H #define OPENMW_ESM_FACT_H #include #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Faction"; } 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 ESM::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.47.0/components/esm/loadglob.cpp000066400000000000000000000017761413061077700216730ustar00rootroot00000000000000#include "loadglob.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Global::sRecordId = REC_GLOB; void Global::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; mId = esm.getHNString ("NAME"); if (esm.isNextSub ("DELE")) { esm.skipHSub(); isDeleted = true; } else { mValue.read (esm, 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, ESM::Variant::Format_Global); } } void Global::blank() { mValue.setType (ESM::VT_None); } bool operator== (const Global& left, const Global& right) { return left.mId==right.mId && left.mValue==right.mValue; } } openmw-openmw-0.47.0/components/esm/loadglob.hpp000066400000000000000000000013011413061077700216600ustar00rootroot00000000000000#ifndef OPENMW_ESM_GLOB_H #define OPENMW_ESM_GLOB_H #include #include "variant.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Global script variables */ struct Global { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Global"; } 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.47.0/components/esm/loadgmst.cpp000066400000000000000000000014671413061077700217170ustar00rootroot00000000000000#include "loadgmst.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int GameSetting::sRecordId = REC_GMST; void GameSetting::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); } void GameSetting::blank() { mValue.setType (ESM::VT_None); } bool operator== (const GameSetting& left, const GameSetting& right) { return left.mValue==right.mValue; } } openmw-openmw-0.47.0/components/esm/loadgmst.hpp000066400000000000000000000013231413061077700217130ustar00rootroot00000000000000#ifndef OPENMW_ESM_GMST_H #define OPENMW_ESM_GMST_H #include #include "variant.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Game setting * */ struct GameSetting { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "GameSetting"; } 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.47.0/components/esm/loadinfo.cpp000066400000000000000000000115721413061077700216760ustar00rootroot00000000000000#include "loadinfo.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; 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().intval) { case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); break; case ESM::FourCC<'O','N','A','M'>::value: mActor = esm.getHString(); break; case ESM::FourCC<'R','N','A','M'>::value: mRace = esm.getHString(); break; case ESM::FourCC<'C','N','A','M'>::value: mClass = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: { mFaction = esm.getHString(); if (mFaction == "FFFF") { mFactionLess = true; } break; } case ESM::FourCC<'A','N','A','M'>::value: mCell = esm.getHString(); break; case ESM::FourCC<'D','N','A','M'>::value: mPcFaction = esm.getHString(); break; case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; case ESM::SREC_NAME: mResponse = esm.getHString(); break; case ESM::FourCC<'S','C','V','R'>::value: { SelectStruct ss; ss.mSelectRule = esm.getHString(); ss.mValue.read(esm, Variant::Format_Info); mSelects.push_back(ss); break; } case ESM::FourCC<'B','N','A','M'>::value: mResultScript = esm.getHString(); break; case ESM::FourCC<'Q','S','T','N'>::value: mQuestStatus = QS_Name; esm.skipRecord(); break; case ESM::FourCC<'Q','S','T','F'>::value: mQuestStatus = QS_Finished; esm.skipRecord(); break; case ESM::FourCC<'Q','S','T','R'>::value: mQuestStatus = QS_Restart; esm.skipRecord(); break; case ESM::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.writeHNCString("DELE", ""); 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.mUnknown1 = 0; mData.mDisposition = 0; mData.mRank = 0; mData.mGender = 0; mData.mPCrank = 0; mData.mUnknown2 = 0; 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.47.0/components/esm/loadinfo.hpp000066400000000000000000000055331413061077700217030ustar00rootroot00000000000000#ifndef OPENMW_ESM_INFO_H #define OPENMW_ESM_INFO_H #include #include #include "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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "DialInfo"; } enum Gender { Male = 0, Female = 1, NA = -1 }; struct DATAstruct { int mUnknown1; union { int mDisposition; // Used for dialogue responses int mJournalIndex; // Used for journal entries }; signed char mRank; // Rank of NPC signed char mGender; // See Gender enum signed char mPCrank; // Player rank signed char mUnknown2; }; // 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.47.0/components/esm/loadingr.cpp000066400000000000000000000062561413061077700217050ustar00rootroot00000000000000#include "loadingr.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; void Ingredient::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'I','R','D','T'>::value: esm.getHT(mData, 56); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadingr.hpp000066400000000000000000000016111413061077700217000ustar00rootroot00000000000000#ifndef OPENMW_ESM_INGR_H #define OPENMW_ESM_INGR_H #include namespace ESM { class ESMReader; class ESMWriter; /* * Alchemy ingredient */ struct Ingredient { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadland.cpp000066400000000000000000000310711413061077700216550ustar00rootroot00000000000000#include "loadland.hpp" #include #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Land::sRecordId = REC_LAND; Land::Land() : mFlags(0) , mX(0) , mY(0) , mPlugin(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; mPlugin = esm.getIndex(); bool hasLocation = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','T','V'>::value: esm.getSubHeaderIs(8); esm.getT(mX); esm.getT(mY); hasLocation = true; break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mFlags); break; case ESM::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().intval) { case ESM::FourCC<'V','N','M','L'>::value: esm.skipHSub(); mDataTypes |= DATA_VNML; break; case ESM::FourCC<'V','H','G','T'>::value: esm.skipHSub(); mDataTypes |= DATA_VHGT; break; case ESM::FourCC<'W','N','A','M'>::value: esm.getHExact(mWnam, sizeof(mWnam)); mDataTypes |= DATA_WNAM; break; case ESM::FourCC<'V','C','L','R'>::value: esm.skipHSub(); mDataTypes |= DATA_VCLR; break; case ESM::FourCC<'V','T','E','X'>::value: 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.writeHNCString("DELE", ""); 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(ESM::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) * ESM::Land::LAND_SIZE + int(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; height = std::min(max, std::max(min, height)); 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() { mPlugin = 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; } ESM::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(ESM::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), mPlugin (land.mPlugin), 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 (mPlugin, land.mPlugin); 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.47.0/components/esm/loadland.hpp000066400000000000000000000120171413061077700216610ustar00rootroot00000000000000#ifndef OPENMW_ESM_LAND_H #define OPENMW_ESM_LAND_H #include #include #include "esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Landscape data. */ struct Land { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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. int mPlugin; // Plugin index, used to reference the correct material palette. // 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 const int DEFAULT_HEIGHT = -2048; // number of vertices per side static const int LAND_SIZE = 65; // cell terrain size in world coords static const int REAL_SIZE = Constants::CellSizeInUnits; // total number of vertices static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; static const int HEIGHT_SCALE = 8; //number of textures per side of land static const int LAND_TEXTURE_SIZE = 16; //total number of textures per land static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; static const int LAND_GLOBAL_MAP_LOD_SIZE = 81; static const 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 ESM::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(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const; mutable LandData *mLandData; }; } #endif openmw-openmw-0.47.0/components/esm/loadlevlist.cpp000066400000000000000000000065661413061077700224340ustar00rootroot00000000000000#include "loadlevlist.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { void LevelledListBase::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasList = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mFlags); break; case ESM::FourCC<'N','N','A','M'>::value: esm.getHT(mChanceNone); break; case ESM::FourCC<'I','N','D','X'>::value: { 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(mRecName); esm.getHNT(li.mLevel, "INTV"); } hasList = true; break; } case ESM::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, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNCString("DELE", ""); 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(mRecName, it->mId); esm.writeHNT("INTV", it->mLevel); } } void LevelledListBase::blank() { mFlags = 0; mChanceNone = 0; mList.clear(); } unsigned int CreatureLevList::sRecordId = REC_LEVC; unsigned int ItemLevList::sRecordId = REC_LEVI; } openmw-openmw-0.47.0/components/esm/loadlevlist.hpp000066400000000000000000000043411413061077700224260ustar00rootroot00000000000000#ifndef OPENMW_ESM_LEVLISTS_H #define OPENMW_ESM_LEVLISTS_H #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) std::string mId; // Record name used to read references. Must be set before load() is // called. const char *mRecName; struct LevelItem { std::string mId; short mLevel; }; std::vector mList; 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 CreatureLevList: LevelledListBase { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "CreatureLevList"; } enum Flags { AllLevels = 0x01 // Calculate from all levels <= player // level, not just the closest below // player. }; CreatureLevList() { mRecName = "CNAM"; } }; struct ItemLevList: LevelledListBase { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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. }; ItemLevList() { mRecName = "INAM"; } }; } #endif openmw-openmw-0.47.0/components/esm/loadligh.cpp000066400000000000000000000051101413061077700216550ustar00rootroot00000000000000#include "loadligh.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Light::sRecordId = REC_LIGH; void Light::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::FourCC<'L','H','D','T'>::value: esm.getHT(mData, 24); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadligh.hpp000066400000000000000000000025351413061077700216720ustar00rootroot00000000000000#ifndef OPENMW_ESM_LIGH_H #define OPENMW_ESM_LIGH_H #include namespace ESM { class ESMReader; class ESMWriter; /* * Lights. Includes static light sources and also carryable candles * and torches. */ struct Light { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadlock.cpp000066400000000000000000000045311413061077700216700ustar00rootroot00000000000000#include "loadlock.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; void Lockpick::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'L','K','D','T'>::value: esm.getHT(mData, 16); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.47.0/components/esm/loadlock.hpp000066400000000000000000000013531413061077700216740ustar00rootroot00000000000000#ifndef OPENMW_ESM_LOCK_H #define OPENMW_ESM_LOCK_H #include namespace ESM { class ESMReader; class ESMWriter; struct Lockpick { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Lockpick"; } struct Data { float mWeight; int mValue; float mQuality; int mUses; }; // Size = 16 Data mData; 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.47.0/components/esm/loadltex.cpp000066400000000000000000000032271413061077700217150ustar00rootroot00000000000000#include "loadltex.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; void LandTexture::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasIndex = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'I','N','T','V'>::value: esm.getHT(mIndex); hasIndex = true; break; case ESM::FourCC<'D','A','T','A'>::value: mTexture = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); } } void LandTexture::blank() { mId.clear(); mTexture.clear(); } } openmw-openmw-0.47.0/components/esm/loadltex.hpp000066400000000000000000000017261413061077700217240ustar00rootroot00000000000000#ifndef OPENMW_ESM_LTEX_H #define OPENMW_ESM_LTEX_H #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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.47.0/components/esm/loadmgef.cpp000066400000000000000000000465361413061077700216710ustar00rootroot00000000000000#include "loadmgef.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace { static const char *sIds[ESM::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 { unsigned int MagicEffect::sRecordId = REC_MGEF; void MagicEffect::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); esm.getHNT(mData, "MEDT", 36); 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::value: mIcon = esm.getHString(); break; case ESM::FourCC<'P','T','E','X'>::value: mParticle = esm.getHString(); break; case ESM::FourCC<'B','S','N','D'>::value: mBoltSound = esm.getHString(); break; case ESM::FourCC<'C','S','N','D'>::value: mCastSound = esm.getHString(); break; case ESM::FourCC<'H','S','N','D'>::value: mHitSound = esm.getHString(); break; case ESM::FourCC<'A','S','N','D'>::value: mAreaSound = esm.getHString(); break; case ESM::FourCC<'C','V','F','X'>::value: mCasting = esm.getHString(); break; case ESM::FourCC<'B','V','F','X'>::value: mBolt = esm.getHString(); break; case ESM::FourCC<'H','V','F','X'>::value: mHit = esm.getHString(); break; case ESM::FourCC<'A','V','F','X'>::value: mArea = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; default: esm.fail("Unknown subrecord"); } } } void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("MEDT", mData, 36); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCString("PTEX", mParticle); esm.writeHNOCString("BSND", mBoltSound); esm.writeHNOCString("CSND", mCastSound); esm.writeHNOCString("HSND", mHitSound); esm.writeHNOCString("ASND", mAreaSound); esm.writeHNOCString("CVFX", mCasting); esm.writeHNOCString("BVFX", mBolt); esm.writeHNOCString("HVFX", mHit); esm.writeHNOCString("AVFX", mArea); esm.writeHNOString("DESC", mDescription); } short MagicEffect::getResistanceEffect(short effect) { // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute // static std::map effects; if (effects.empty()) { effects[DisintegrateArmor] = Sanctuary; effects[DisintegrateWeapon] = Sanctuary; for (int i=0; i<5; ++i) effects[DrainAttribute+i] = ResistMagicka; for (int i=0; i<5; ++i) effects[DamageAttribute+i] = ResistMagicka; for (int i=0; i<5; ++i) effects[AbsorbAttribute+i] = ResistMagicka; for (int i=0; i<10; ++i) effects[WeaknessToFire+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=0; i<5; ++i) effects[DrainAttribute+i] = WeaknessToMagicka; for (int i=0; i<5; ++i) effects[DamageAttribute+i] = WeaknessToMagicka; for (int i=0; i<5; ++i) effects[AbsorbAttribute+i] = WeaknessToMagicka; for (int i=0; i<10; ++i) effects[WeaknessToFire+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 { const std::string &mName; public: FindSecond(const std::string &name) : mName(name) { } bool operator()(const std::pair &item) const { if(Misc::StringUtils::ciEqual(item.second, mName)) return true; return false; } }; short MagicEffect::effectStringToId(const std::string &effect) { std::map::const_iterator name; name = std::find_if(sNames.begin(), sNames.end(), FindSecond(effect)); if(name == sNames.end()) throw std::runtime_error(std::string("Unimplemented effect ")+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() { 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 namespace ESM { class ESMReader; class ESMWriter; struct MagicEffect { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "MagicEffect"; } 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(const std::string &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; // ESM::Static std::string mBolt; // ESM::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.47.0/components/esm/loadmisc.cpp000066400000000000000000000045221413061077700216730ustar00rootroot00000000000000#include "loadmisc.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'M','C','D','T'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mWeight = 0; mData.mValue = 0; mData.mIsKey = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.47.0/components/esm/loadmisc.hpp000066400000000000000000000020031413061077700216700ustar00rootroot00000000000000#ifndef OPENMW_ESM_MISC_H #define OPENMW_ESM_MISC_H #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadnpc.cpp000066400000000000000000000170071413061077700215220ustar00rootroot00000000000000#include "loadnpc.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; void NPC::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mPersistent = (esm.getRecordFlags() & 0x0400) != 0; 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().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'R','N','A','M'>::value: mRace = esm.getHString(); break; case ESM::FourCC<'C','N','A','M'>::value: mClass = esm.getHString(); break; case ESM::FourCC<'A','N','A','M'>::value: mFaction = esm.getHString(); break; case ESM::FourCC<'B','N','A','M'>::value: mHead = esm.getHString(); break; case ESM::FourCC<'K','N','A','M'>::value: mHair = esm.getHString(); break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'N','P','D','T'>::value: 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 ESM::FourCC<'F','L','A','G'>::value: hasFlags = true; int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; break; case ESM::FourCC<'N','P','C','S'>::value: mSpells.add(esm); break; case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; case ESM::FourCC<'A','I','D','T'>::value: esm.getHExact(&mAiData, sizeof(mAiData)); break; case ESM::FourCC<'D','O','D','T'>::value: case ESM::FourCC<'D','N','A','M'>::value: 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 ESM::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.writeHNCString("DELE", ""); 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.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() { 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.47.0/components/esm/loadnpc.hpp000066400000000000000000000065441413061077700215330ustar00rootroot00000000000000#ifndef OPENMW_ESM_NPC_H #define OPENMW_ESM_NPC_H #include #include #include "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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; bool mPersistent; InventoryList mInventory; SpellList mSpells; AIData mAiData; Transport mTransport; const std::vector& getTransport() const; AIPackageList mAiPackage; 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.47.0/components/esm/loadpgrd.cpp000066400000000000000000000143301413061077700216720ustar00rootroot00000000000000#include "loadpgrd.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Pathgrid::sRecordId = REC_PGRD; 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().intval) { case ESM::SREC_NAME: mCell = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::FourCC<'P','G','R','P'>::value: { 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 ESM::FourCC<'P','G','R','C'>::value: { 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 ESM::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.writeHNCString("DELE", ""); 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.47.0/components/esm/loadpgrd.hpp000066400000000000000000000031731413061077700217020ustar00rootroot00000000000000#ifndef OPENMW_ESM_PGRD_H #define OPENMW_ESM_PGRD_H #include #include namespace ESM { class ESMReader; class ESMWriter; /* * Path grid. */ struct Pathgrid { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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.47.0/components/esm/loadprob.cpp000066400000000000000000000045151413061077700217040ustar00rootroot00000000000000#include "loadprob.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Probe::sRecordId = REC_PROB; void Probe::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'P','B','D','T'>::value: esm.getHT(mData, 16); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.47.0/components/esm/loadprob.hpp000066400000000000000000000013471413061077700217110ustar00rootroot00000000000000#ifndef OPENMW_ESM_PROBE_H #define OPENMW_ESM_PROBE_H #include namespace ESM { class ESMReader; class ESMWriter; struct Probe { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Probe"; } struct Data { float mWeight; int mValue; float mQuality; int mUses; }; // Size = 16 Data mData; 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.47.0/components/esm/loadrace.cpp000066400000000000000000000052121413061077700216470ustar00rootroot00000000000000#include "loadrace.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Race::sRecordId = REC_RACE; int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; } int Race::MaleFemaleF::getValue (bool male) const { return static_cast(male ? mMale : mFemale); } void Race::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mPowers.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'R','A','D','T'>::value: esm.getHT(mData, 140); hasData = true; break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); } void Race::blank() { 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.47.0/components/esm/loadrace.hpp000066400000000000000000000027531413061077700216630ustar00rootroot00000000000000#ifndef OPENMW_ESM_RACE_H #define OPENMW_ESM_RACE_H #include #include "spelllist.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Race definition */ struct Race { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; int 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; 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.47.0/components/esm/loadregn.cpp000066400000000000000000000074531413061077700217010ustar00rootroot00000000000000#include "loadregn.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Region::sRecordId = REC_REGN; void Region::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'W','E','A','T'>::value: { esm.getSubHeader(); if (esm.getVer() == VER_12) { mData.mA = 0; mData.mB = 0; esm.getExact(&mData, sizeof(mData) - 2); } else if (esm.getVer() == VER_13) { // May include the additional two bytes (but not necessarily) if (esm.getSubSize() == sizeof(mData)) { esm.getExact(&mData, sizeof(mData)); } else { mData.mA = 0; mData.mB = 0; esm.getExact(&mData, sizeof(mData)-2); } } else { esm.fail("Don't know what to do in this version"); } break; } case ESM::FourCC<'B','N','A','M'>::value: mSleepList = esm.getHString(); break; case ESM::FourCC<'C','N','A','M'>::value: esm.getHT(mMapColor); break; case ESM::FourCC<'S','N','A','M'>::value: { esm.getSubHeader(); SoundRef sr; sr.mSound.assign(esm.getString(32)); esm.getT(sr.mChance); mSoundList.push_back(sr); break; } case ESM::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.writeHNCString("DELE", ""); 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("NPCO"); } } void Region::blank() { mData.mClear = mData.mCloudy = mData.mFoggy = mData.mOvercast = mData.mRain = mData.mThunder = mData.mAsh = mData.mBlight = mData.mA = mData.mB = 0; mMapColor = 0; mName.clear(); mSleepList.clear(); mSoundList.clear(); } } openmw-openmw-0.47.0/components/esm/loadregn.hpp000066400000000000000000000026531413061077700217030ustar00rootroot00000000000000#ifndef OPENMW_ESM_REGN_H #define OPENMW_ESM_REGN_H #include #include #include "esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Region data */ struct Region { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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, // Unknown weather, probably snow and something. Only // present in file version 1.3. // the engine uses mA as "snow" and mB as "blizard" mA, mB; }; // 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 // 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.47.0/components/esm/loadrepa.cpp000066400000000000000000000045211413061077700216660ustar00rootroot00000000000000#include "loadrepa.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Repair::sRecordId = REC_REPA; void Repair::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'R','I','D','T'>::value: esm.getHT(mData, 16); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.47.0/components/esm/loadrepa.hpp000066400000000000000000000013471413061077700216760ustar00rootroot00000000000000#ifndef OPENMW_ESM_REPA_H #define OPENMW_ESM_REPA_H #include namespace ESM { class ESMReader; class ESMWriter; struct Repair { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Repair"; } struct Data { float mWeight; int mValue; int mUses; float mQuality; }; // Size = 16 Data mData; 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.47.0/components/esm/loadscpt.cpp000066400000000000000000000144131413061077700217110ustar00rootroot00000000000000#include "loadscpt.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Script::sRecordId = REC_SCPT; 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.toString(); 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.toString(); 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; mVarNames.clear(); bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::FourCC<'S','C','H','D'>::value: { esm.getSubHeader(); mId = esm.getString(32); esm.getT(mData); hasHeader = true; break; } case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); break; case ESM::FourCC<'S','C','D','T'>::value: { // 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 ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadscpt.hpp000066400000000000000000000027721413061077700217230ustar00rootroot00000000000000#ifndef OPENMW_ESM_SCPT_H #define OPENMW_ESM_SCPT_H #include #include #include "esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Script definitions */ class Script { public: static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; }; 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.47.0/components/esm/loadskil.cpp000066400000000000000000000117471413061077700217110ustar00rootroot00000000000000#include "loadskil.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "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 }}; unsigned int Skill::sRecordId = REC_SKIL; void Skill::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); hasIndex = true; break; case ESM::FourCC<'S','K','D','T'>::value: esm.getHT(mData, 24); hasData = true; break; case ESM::FourCC<'D','E','S','C'>::value: 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() { 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 "defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Skill information * */ struct Skill { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Skill"; } 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.47.0/components/esm/loadsndg.cpp000066400000000000000000000036421413061077700216750ustar00rootroot00000000000000#include "loadsndg.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; break; case ESM::FourCC<'C','N','A','M'>::value: mCreature = esm.getHString(); break; case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); } void SoundGenerator::blank() { mType = LeftFoot; mCreature.clear(); mSound.clear(); } } openmw-openmw-0.47.0/components/esm/loadsndg.hpp000066400000000000000000000014741413061077700217030ustar00rootroot00000000000000#ifndef OPENMW_ESM_SNDG_H #define OPENMW_ESM_SNDG_H #include namespace ESM { class ESMReader; class ESMWriter; /* * Sound generator. This describes the sounds a creature make. */ struct SoundGenerator { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "SoundGenerator"; } enum Type { LeftFoot = 0, RightFoot = 1, SwimLeft = 2, SwimRight = 3, Moan = 4, Roar = 5, Scream = 6, Land = 7 }; // Type int mType; 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.47.0/components/esm/loadsoun.cpp000066400000000000000000000033441413061077700217250ustar00rootroot00000000000000#include "loadsoun.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; void Sound::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 3); hasData = true; break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } void Sound::blank() { mSound.clear(); mData.mVolume = 128; mData.mMinRange = 0; mData.mMaxRange = 255; } } openmw-openmw-0.47.0/components/esm/loadsoun.hpp000066400000000000000000000012401413061077700217230ustar00rootroot00000000000000#ifndef OPENMW_ESM_SOUN_H #define OPENMW_ESM_SOUN_H #include namespace ESM { class ESMReader; class ESMWriter; struct SOUNstruct { unsigned char mVolume, mMinRange, mMaxRange; }; struct Sound { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Sound"; } SOUNstruct mData; 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.47.0/components/esm/loadspel.cpp000066400000000000000000000040011413061077700216730ustar00rootroot00000000000000#include "loadspel.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; void Spell::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'S','P','D','T'>::value: esm.getHT(mData, 12); hasData = true; break; case ESM::FourCC<'E','N','A','M'>::value: ENAMstruct s; esm.getHT(s, 24); mEffects.mList.push_back(s); break; case ESM::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.writeHNCString("DELE", ""); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); } void Spell::blank() { mData.mType = 0; mData.mCost = 0; mData.mFlags = 0; mName.clear(); mEffects.mList.clear(); } } openmw-openmw-0.47.0/components/esm/loadspel.hpp000066400000000000000000000024571413061077700217150ustar00rootroot00000000000000#ifndef OPENMW_ESM_SPEL_H #define OPENMW_ESM_SPEL_H #include #include "effectlist.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Spell { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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.47.0/components/esm/loadsscr.cpp000066400000000000000000000027611413061077700217150ustar00rootroot00000000000000#include "loadsscr.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; void StartScript::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'D','A','T','A'>::value: mData = esm.getHString(); hasData = true; break; case ESM::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.writeHNCString("DELE", ""); } else { esm.writeHNString("DATA", mData); } } void StartScript::blank() { mData.clear(); } } openmw-openmw-0.47.0/components/esm/loadsscr.hpp000066400000000000000000000015461413061077700217220ustar00rootroot00000000000000#ifndef OPENMW_ESM_SSCR_H #define OPENMW_ESM_SSCR_H #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "StartScript"; } std::string mData; 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.47.0/components/esm/loadstat.cpp000066400000000000000000000025371413061077700217170ustar00rootroot00000000000000#include "loadstat.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Static::sRecordId = REC_STAT; void Static::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); } else { esm.writeHNCString("MODL", mModel); } } void Static::blank() { mModel.clear(); } } openmw-openmw-0.47.0/components/esm/loadstat.hpp000066400000000000000000000021161413061077700217150ustar00rootroot00000000000000#ifndef OPENMW_ESM_STAT_H #define OPENMW_ESM_STAT_H #include 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 { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Static"; } 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.47.0/components/esm/loadtes3.cpp000066400000000000000000000036161413061077700216210ustar00rootroot00000000000000#include "loadtes3.hpp" #include "esmcommon.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" void ESM::Header::blank() { mData.version = ESM::VER_13; mData.type = 0; mData.author.clear(); mData.desc.clear(); mData.records = 0; mFormat = CurrentFormat; mMaster.clear(); } void ESM::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.getString(32) ); mData.desc.assign( esm.getString(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 ESM::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.47.0/components/esm/loadtes3.hpp000066400000000000000000000031521413061077700216210ustar00rootroot00000000000000#ifndef COMPONENT_ESM_TES3_H #define COMPONENT_ESM_TES3_H #include #include "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 const int CurrentFormat = 0; // 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.47.0/components/esm/loadweap.cpp000066400000000000000000000053731413061077700217010ustar00rootroot00000000000000#include "loadweap.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; void Weapon::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().intval) { case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; case ESM::FourCC<'W','P','D','T'>::value: esm.getHT(mData, 32); hasData = true; break; case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; case ESM::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.writeHNCString("DELE", ""); 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() { 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.47.0/components/esm/loadweap.hpp000066400000000000000000000043151413061077700217010ustar00rootroot00000000000000#ifndef OPENMW_ESM_WEAP_H #define OPENMW_ESM_WEAP_H #include #include "loadskil.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Weapon definition */ struct Weapon { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string 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; 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; ESM::Skill::SkillEnum mSkill; Class mWeaponClass; int mAmmoType; int mFlags; }; } #endif openmw-openmw-0.47.0/components/esm/locals.cpp000066400000000000000000000011721413061077700213530ustar00rootroot00000000000000#include "locals.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::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 ESM::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.47.0/components/esm/locals.hpp000066400000000000000000000007621413061077700213640ustar00rootroot00000000000000#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.47.0/components/esm/magiceffects.cpp000066400000000000000000000010611413061077700225130ustar00rootroot00000000000000#include "magiceffects.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void MagicEffects::save(ESMWriter &esm) const { for (std::map::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { esm.writeHNT("EFID", it->first); esm.writeHNT("BASE", it->second); } } void MagicEffects::load(ESMReader &esm) { while (esm.isNextSub("EFID")) { int id, base; esm.getHT(id); esm.getHNT(base, "BASE"); mEffects.insert(std::make_pair(id, base)); } } } openmw-openmw-0.47.0/components/esm/magiceffects.hpp000066400000000000000000000025361413061077700225300ustar00rootroot00000000000000#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.47.0/components/esm/mappings.cpp000066400000000000000000000106551413061077700217220ustar00rootroot00000000000000#include "mappings.hpp" #include namespace ESM { ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type) { switch(type) { case ESM::PRT_Head: return ESM::BodyPart::MP_Head; case ESM::PRT_Hair: return ESM::BodyPart::MP_Hair; case ESM::PRT_Neck: return ESM::BodyPart::MP_Neck; case ESM::PRT_Cuirass: return ESM::BodyPart::MP_Chest; case ESM::PRT_Groin: return ESM::BodyPart::MP_Groin; case ESM::PRT_RHand: return ESM::BodyPart::MP_Hand; case ESM::PRT_LHand: return ESM::BodyPart::MP_Hand; case ESM::PRT_RWrist: return ESM::BodyPart::MP_Wrist; case ESM::PRT_LWrist: return ESM::BodyPart::MP_Wrist; case ESM::PRT_RForearm: return ESM::BodyPart::MP_Forearm; case ESM::PRT_LForearm: return ESM::BodyPart::MP_Forearm; case ESM::PRT_RUpperarm: return ESM::BodyPart::MP_Upperarm; case ESM::PRT_LUpperarm: return ESM::BodyPart::MP_Upperarm; case ESM::PRT_RFoot: return ESM::BodyPart::MP_Foot; case ESM::PRT_LFoot: return ESM::BodyPart::MP_Foot; case ESM::PRT_RAnkle: return ESM::BodyPart::MP_Ankle; case ESM::PRT_LAnkle: return ESM::BodyPart::MP_Ankle; case ESM::PRT_RKnee: return ESM::BodyPart::MP_Knee; case ESM::PRT_LKnee: return ESM::BodyPart::MP_Knee; case ESM::PRT_RLeg: return ESM::BodyPart::MP_Upperleg; case ESM::PRT_LLeg: return ESM::BodyPart::MP_Upperleg; case ESM::PRT_Tail: return ESM::BodyPart::MP_Tail; default: throw std::runtime_error("PartReferenceType " + std::to_string(type) + " not associated with a mesh part"); } } std::string getBoneName(ESM::PartReferenceType type) { switch(type) { case ESM::PRT_Head: return "head"; case ESM::PRT_Hair: return "head"; // This is purposeful. case ESM::PRT_Neck: return "neck"; case ESM::PRT_Cuirass: return "chest"; case ESM::PRT_Groin: return "groin"; case ESM::PRT_Skirt: return "groin"; case ESM::PRT_RHand: return "right hand"; case ESM::PRT_LHand: return "left hand"; case ESM::PRT_RWrist: return "right wrist"; case ESM::PRT_LWrist: return "left wrist"; case ESM::PRT_Shield: return "shield bone"; case ESM::PRT_RForearm: return "right forearm"; case ESM::PRT_LForearm: return "left forearm"; case ESM::PRT_RUpperarm: return "right upper arm"; case ESM::PRT_LUpperarm: return "left upper arm"; case ESM::PRT_RFoot: return "right foot"; case ESM::PRT_LFoot: return "left foot"; case ESM::PRT_RAnkle: return "right ankle"; case ESM::PRT_LAnkle: return "left ankle"; case ESM::PRT_RKnee: return "right knee"; case ESM::PRT_LKnee: return "left knee"; case ESM::PRT_RLeg: return "right upper leg"; case ESM::PRT_LLeg: return "left upper leg"; case ESM::PRT_RPauldron: return "right clavicle"; case ESM::PRT_LPauldron: return "left clavicle"; case ESM::PRT_Weapon: return "weapon bone"; case ESM::PRT_Tail: return "tail"; default: throw std::runtime_error("unknown PartReferenceType"); } } std::string getMeshFilter(ESM::PartReferenceType type) { switch(type) { case ESM::PRT_Hair: return "hair"; default: return getBoneName(type); } } } openmw-openmw-0.47.0/components/esm/mappings.hpp000066400000000000000000000005651413061077700217260ustar00rootroot00000000000000#ifndef OPENMW_ESM_MAPPINGS_H #define OPENMW_ESM_MAPPINGS_H #include #include #include namespace ESM { ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type); std::string getBoneName(ESM::PartReferenceType type); std::string getMeshFilter(ESM::PartReferenceType type); } #endif openmw-openmw-0.47.0/components/esm/npcstate.cpp000066400000000000000000000011571413061077700217220ustar00rootroot00000000000000#include "npcstate.hpp" void ESM::NpcState::load (ESMReader &esm) { ObjectState::load (esm); if (mHasCustomState) { mInventory.load (esm); mNpcStats.load (esm); mCreatureStats.load (esm); } } void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); if (mHasCustomState) { mInventory.save (esm); mNpcStats.save (esm); mCreatureStats.save (esm); } } void ESM::NpcState::blank() { ObjectState::blank(); mNpcStats.blank(); mCreatureStats.blank(); mHasCustomState = true; } openmw-openmw-0.47.0/components/esm/npcstate.hpp000066400000000000000000000014061413061077700217240ustar00rootroot00000000000000#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.47.0/components/esm/npcstats.cpp000066400000000000000000000122061413061077700217350ustar00rootroot00000000000000#include #include "npcstats.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} void ESM::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) { ESM::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) { ESM::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 ESM::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 ESM::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.47.0/components/esm/npcstats.hpp000066400000000000000000000020301413061077700217340ustar00rootroot00000000000000#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.47.0/components/esm/objectstate.cpp000066400000000000000000000076011413061077700224100ustar00rootroot00000000000000#include "objectstate.hpp" #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); bool isDeleted; mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); if (mHasLocals) mLocals.load (esm); mEnabled = 1; esm.getHNOT (mEnabled, "ENAB"); mCount = 1; esm.getHNOT (mCount, "COUN"); mPosition = mRef.mPos; esm.getHNOT (mPosition, "POS_", 24); 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 ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const { mRef.save (esm, true, inInventory); if (mHasLocals) { esm.writeHNT ("HLOC", mHasLocals); mLocals.save (esm); } if (!mEnabled && !inInventory) esm.writeHNT ("ENAB", mEnabled); if (mCount!=1) esm.writeHNT ("COUN", mCount); if (!inInventory && mPosition != mRef.mPos) esm.writeHNT ("POS_", mPosition, 24); if (mFlags != 0) esm.writeHNT ("FLAG", mFlags); mAnimationState.save(esm); if (!mHasCustomState) esm.writeHNT ("HCUS", false); } void ESM::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 ESM::NpcState& ESM::ObjectState::asNpcState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } ESM::NpcState& ESM::ObjectState::asNpcState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } const ESM::CreatureState& ESM::ObjectState::asCreatureState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } ESM::CreatureState& ESM::ObjectState::asCreatureState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } const ESM::ContainerState& ESM::ObjectState::asContainerState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } ESM::ContainerState& ESM::ObjectState::asContainerState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } const ESM::DoorState& ESM::ObjectState::asDoorState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } ESM::DoorState& ESM::ObjectState::asDoorState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } const ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } ESM::ObjectState::~ObjectState() {} openmw-openmw-0.47.0/components/esm/objectstate.hpp000066400000000000000000000036001413061077700224100ustar00rootroot00000000000000#ifndef OPENMW_ESM_OBJECTSTATE_H #define OPENMW_ESM_OBJECTSTATE_H #include #include #include "cellref.hpp" #include "locals.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; unsigned char mEnabled; int mCount; ESM::Position mPosition; unsigned int mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; unsigned int mVersion; ESM::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.47.0/components/esm/player.cpp000066400000000000000000000040701413061077700213720ustar00rootroot00000000000000#include "player.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::Player::load (ESMReader &esm) { mObject.mRef.loadId(esm, true); mObject.load (esm); mCellId.load (esm); esm.getHNT (mLastKnownExteriorPosition, "LKEP", 12); if (esm.isNextSub ("MARK")) { mHasMark = true; esm.getHT (mMarkedPosition, 24); 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; } bool intFallback = esm.getFormat() < 11; if (esm.hasMoreSubs()) { for (int i=0; ifirst); esm.writeHNString ("PREV", it->second); } for (int i=0; i #include "npcstate.hpp" #include "cellid.hpp" #include "defs.hpp" #include "loadskil.hpp" #include "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; ESM::Position mMarkedPosition; CellId mMarkedCell; std::string mBirthsign; int mCurrentCrimeId; int mPaidCrimeId; StatState mSaveAttributes[ESM::Attribute::Length]; StatState mSaveSkills[ESM::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.47.0/components/esm/projectilestate.cpp000066400000000000000000000033771413061077700233100ustar00rootroot00000000000000#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); } void MagicBoltState::load(ESMReader &esm) { BaseProjectileState::load(esm); mSpellId = esm.getHNString("SPEL"); if (esm.isNextSub("SRCN")) // for backwards compatibility esm.skipHSub(); ESM::EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); 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.47.0/components/esm/projectilestate.hpp000066400000000000000000000016201413061077700233020ustar00rootroot00000000000000#ifndef OPENMW_ESM_PROJECTILESTATE_H #define OPENMW_ESM_PROJECTILESTATE_H #include #include #include #include "effectlist.hpp" #include "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; 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.47.0/components/esm/queststate.cpp000066400000000000000000000006241413061077700223010ustar00rootroot00000000000000#include "queststate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::QuestState::load (ESMReader &esm) { mTopic = esm.getHNString ("YETO"); esm.getHNOT (mState, "QSTA"); esm.getHNOT (mFinished, "QFIN"); } void ESM::QuestState::save (ESMWriter &esm) const { esm.writeHNString ("YETO", mTopic); esm.writeHNT ("QSTA", mState); esm.writeHNT ("QFIN", mFinished); } openmw-openmw-0.47.0/components/esm/queststate.hpp000066400000000000000000000006201413061077700223020ustar00rootroot00000000000000#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.47.0/components/esm/quickkeys.cpp000066400000000000000000000020151413061077700221030ustar00rootroot00000000000000#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.47.0/components/esm/quickkeys.hpp000066400000000000000000000007141413061077700221140ustar00rootroot00000000000000#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.47.0/components/esm/records.hpp000066400000000000000000000021661413061077700215500ustar00rootroot00000000000000#ifndef OPENMW_ESM_RECORDS_H #define OPENMW_ESM_RECORDS_H #include "defs.hpp" #include "loadacti.hpp" #include "loadalch.hpp" #include "loadappa.hpp" #include "loadarmo.hpp" #include "loadbody.hpp" #include "loadbook.hpp" #include "loadbsgn.hpp" #include "loadcell.hpp" #include "loadclas.hpp" #include "loadclot.hpp" #include "loadcont.hpp" #include "loadcrea.hpp" #include "loadinfo.hpp" #include "loaddial.hpp" #include "loaddoor.hpp" #include "loadench.hpp" #include "loadfact.hpp" #include "loadglob.hpp" #include "loadgmst.hpp" #include "loadingr.hpp" #include "loadland.hpp" #include "loadlevlist.hpp" #include "loadligh.hpp" #include "loadlock.hpp" #include "loadrepa.hpp" #include "loadprob.hpp" #include "loadltex.hpp" #include "loadmgef.hpp" #include "loadmisc.hpp" #include "loadnpc.hpp" #include "loadpgrd.hpp" #include "loadrace.hpp" #include "loadregn.hpp" #include "loadscpt.hpp" #include "loadskil.hpp" #include "loadsndg.hpp" #include "loadsoun.hpp" #include "loadspel.hpp" #include "loadsscr.hpp" #include "loadstat.hpp" #include "loadweap.hpp" // Special records which are not loaded from ESM #include "attr.hpp" #endif openmw-openmw-0.47.0/components/esm/savedgame.cpp000066400000000000000000000030301413061077700220250ustar00rootroot00000000000000#include "savedgame.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; int ESM::SavedGame::sCurrentFormat = 15; void ESM::SavedGame::load (ESMReader &esm) { mPlayerName = esm.getHNString("PLNA"); esm.getHNOT (mPlayerLevel, "PLLE"); mPlayerClassId = esm.getHNOString("PLCL"); mPlayerClassName = esm.getHNOString("PLCN"); mPlayerCell = esm.getHNString("PLCE"); esm.getHNT (mInGameTime, "TSTM", 16); 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 ESM::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.47.0/components/esm/savedgame.hpp000066400000000000000000000017231413061077700220410ustar00rootroot00000000000000#ifndef OPENMW_ESM_SAVEDGAME_H #define OPENMW_ESM_SAVEDGAME_H #include #include #include "defs.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct SavedGame { static unsigned int sRecordId; 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.47.0/components/esm/spelllist.cpp000066400000000000000000000011551413061077700221120ustar00rootroot00000000000000#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.47.0/components/esm/spelllist.hpp000066400000000000000000000011711413061077700221150ustar00rootroot00000000000000#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.47.0/components/esm/spellstate.cpp000066400000000000000000000070511413061077700222600ustar00rootroot00000000000000#include "spellstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void SpellState::load(ESMReader &esm) { 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); } mSpells[id] = state; } // 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 (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) { esm.writeHNString("SPEL", it->first); const std::map& random = it->second.mEffectRands; for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) { esm.writeHNT("INDX", rIt->first); esm.writeHNT("RAND", rIt->second); } const std::set& purges = it->second.mPurgedEffects; for (std::set::const_iterator pIt = purges.begin(); pIt != purges.end(); ++pIt) esm.writeHNT("PURG", *pIt); } for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) { esm.writeHNString("CORP", it->first); const CorprusStats & stats = it->second; esm.writeHNT("WORS", stats.mWorsenings); esm.writeHNT("TIME", stats.mNextWorsening); } 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.47.0/components/esm/spellstate.hpp000066400000000000000000000022061413061077700222620ustar00rootroot00000000000000#ifndef OPENMW_ESM_SPELLSTATE_H #define OPENMW_ESM_SPELLSTATE_H #include #include #include #include #include "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; }; typedef std::map TContainer; TContainer mSpells; // FIXME: obsolete, used only for old saves 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.47.0/components/esm/statstate.cpp000066400000000000000000000034101413061077700221070ustar00rootroot00000000000000#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 ESM::StatState; template struct ESM::StatState; openmw-openmw-0.47.0/components/esm/statstate.hpp000066400000000000000000000011371413061077700221200ustar00rootroot00000000000000#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.47.0/components/esm/stolenitems.cpp000066400000000000000000000027611413061077700224510ustar00rootroot00000000000000#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")) { std::string subname = esm.retSubName().toString(); std::string owner = esm.getHString(); bool isFaction = (subname == "FNAM"); int count; esm.getHNT(count, "COUN"); ownerMap.insert(std::make_pair(std::make_pair(owner, isFaction), count)); } mStolenItems[itemid] = ownerMap; } } } openmw-openmw-0.47.0/components/esm/stolenitems.hpp000066400000000000000000000007501413061077700224520ustar00rootroot00000000000000#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(ESM::ESMReader& esm); void write(ESM::ESMWriter& esm) const; }; } #endif openmw-openmw-0.47.0/components/esm/transport.cpp000066400000000000000000000021021413061077700221240ustar00rootroot00000000000000#include "transport.hpp" #include #include #include namespace ESM { void Transport::add(ESMReader &esm) { if (esm.retSubName().intval == ESM::FourCC<'D','O','D','T'>::value) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value) { 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.47.0/components/esm/transport.hpp000066400000000000000000000011561413061077700221410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_TRANSPORT_H #define OPENMW_COMPONENTS_ESM_TRANSPORT_H #include #include #include "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.47.0/components/esm/util.hpp000066400000000000000000000014351413061077700210620ustar00rootroot00000000000000#ifndef OPENMW_ESM_UTIL_H #define OPENMW_ESM_UTIL_H #include #include namespace ESM { // format 0, savegames only struct Quaternion { float mValues[4]; Quaternion() {} 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() {} 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.47.0/components/esm/variant.cpp000066400000000000000000000140511413061077700215420ustar00rootroot00000000000000#include "variant.hpp" #include #include #include "esmreader.hpp" #include "variantimp.hpp" #include "defs.hpp" namespace { const uint32_t STRV = ESM::FourCC<'S','T','R','V'>::value; const uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value; const uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value; const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; template struct GetValue { T operator()(int value) const { return static_cast(value); } T operator()(float value) const { return static_cast(value); } template 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"); } }; } std::string ESM::Variant::getString() const { return std::get(mData); } int ESM::Variant::getInteger() const { return std::visit(GetValue{}, mData); } float ESM::Variant::getFloat() const { return std::visit(GetValue{}, mData); } void ESM::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 ESM::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 ESM::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 ESM::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 ESM::Variant::setString (const std::string& value) { std::get(mData) = value; } void ESM::Variant::setString (std::string&& value) { std::get(mData) = std::move(value); } void ESM::Variant::setInteger (int value) { std::visit(SetValue(value), mData); } void ESM::Variant::setFloat (float value) { std::visit(SetValue(value), mData); } std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) { value.write (stream); return stream; } openmw-openmw-0.47.0/components/esm/variant.hpp000066400000000000000000000055061413061077700215540ustar00rootroot00000000000000#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; } 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.47.0/components/esm/variantimp.cpp000066400000000000000000000112771413061077700222570ustar00rootroot00000000000000#include "variantimp.hpp" #include #include #include "esmreader.hpp" #include "esmwriter.hpp" void ESM::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 ESM::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 ESM::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 ESM::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 ESM::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 ESM::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.47.0/components/esm/variantimp.hpp000066400000000000000000000034311413061077700222550ustar00rootroot00000000000000#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.47.0/components/esm/weatherstate.cpp000066400000000000000000000052741413061077700226050ustar00rootroot00000000000000#include "weatherstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace { const char* currentRegionRecord = "CREG"; const char* timePassedRecord = "TMPS"; const char* fastForwardRecord = "FAST"; const char* weatherUpdateTimeRecord = "WUPD"; const char* transitionFactorRecord = "TRFC"; const char* currentWeatherRecord = "CWTH"; const char* nextWeatherRecord = "NWTH"; const char* queuedWeatherRecord = "QWTH"; const char* regionNameRecord = "RGNN"; const char* regionWeatherRecord = "RGNW"; const char* 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.c_str()); 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.47.0/components/esm/weatherstate.hpp000066400000000000000000000013461413061077700226060ustar00rootroot00000000000000#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.47.0/components/esmterrain/000077500000000000000000000000001413061077700207565ustar00rootroot00000000000000openmw-openmw-0.47.0/components/esmterrain/storage.cpp000066400000000000000000000551251413061077700231360ustar00rootroot00000000000000#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.47.0/components/esmterrain/storage.hpp000066400000000000000000000150471413061077700231420ustar00rootroot00000000000000#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->mPlugin; } 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.47.0/components/fallback/000077500000000000000000000000001413061077700203445ustar00rootroot00000000000000openmw-openmw-0.47.0/components/fallback/fallback.cpp000066400000000000000000000043011413061077700226050ustar00rootroot00000000000000#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.47.0/components/fallback/fallback.hpp000066400000000000000000000013331413061077700226140ustar00rootroot00000000000000#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.47.0/components/fallback/validate.cpp000066400000000000000000000013521413061077700226420ustar00rootroot00000000000000#include "validate.hpp" 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) { std::string temp = Files::EscapeHashString::processString(token); size_t sep = temp.find(','); if (sep < 1 || sep == temp.length() - 1 || sep == std::string::npos) throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); std::string key(temp.substr(0, sep)); std::string value(temp.substr(sep + 1)); map->mMap[key] = value; } } openmw-openmw-0.47.0/components/fallback/validate.hpp000066400000000000000000000010721413061077700226460ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FALLBACK_VALIDATE_H #define OPENMW_COMPONENTS_FALLBACK_VALIDATE_H #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 Fallback { struct FallbackMap { std::map mMap; }; void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int); } #endif openmw-openmw-0.47.0/components/files/000077500000000000000000000000001413061077700177075ustar00rootroot00000000000000openmw-openmw-0.47.0/components/files/androidpath.cpp000066400000000000000000000041741413061077700227160ustar00rootroot00000000000000#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.47.0/components/files/androidpath.hpp000066400000000000000000000022651413061077700227220ustar00rootroot00000000000000#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.47.0/components/files/collections.cpp000066400000000000000000000050451413061077700227350ustar00rootroot00000000000000#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 { MultiDirCollectionContainer::iterator iter = mCollections.find(extension); if (iter==mCollections.end()) { std::pair result = mCollections.insert(std::make_pair(extension, MultiDirCollection(mDirectories, extension, 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.47.0/components/files/collections.hpp000066400000000000000000000026001413061077700227340ustar00rootroot00000000000000#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.47.0/components/files/configurationmanager.cpp000066400000000000000000000246531413061077700246270ustar00rootroot00000000000000#include "configurationmanager.hpp" #include #include #include #include /** * \namespace Files */ namespace Files { 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 userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager(bool silent) : mFixedPath(applicationName) , mSilent(silent) { setupTokensMapping(); boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); boost::filesystem::create_directories(mFixedPath.getUserDataPath()); mLogPath = mFixedPath.getUserConfigPath(); mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails boost::system::error_code dirErr; if (!boost::filesystem::create_directories(mScreenshotPath, dirErr) && !boost::filesystem::is_directory(mScreenshotPath)) { mScreenshotPath = mFixedPath.getUserDataPath(); } } ConfigurationManager::~ConfigurationManager() { } void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet) { bool silent = mSilent; mSilent = quiet; // User config has the highest priority. auto composingVariables = separateComposingVariables(variables, description); loadConfig(mFixedPath.getUserConfigPath(), variables, description); mergeComposingVariables(variables, composingVariables, description); boost::program_options::notify(variables); // read either local or global config depending on type of installation composingVariables = separateComposingVariables(variables, description); bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); mergeComposingVariables(variables, composingVariables, description); boost::program_options::notify(variables); if (!loaded) { composingVariables = separateComposingVariables(variables, description); loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); mergeComposingVariables(variables, composingVariables, description); boost::program_options::notify(variables); } mSilent = silent; } boost::program_options::variables_map ConfigurationManager::separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) { boost::program_options::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 ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::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().toStdStringVector(); 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 = 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::EscapePathContainer)) { 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(Files::EscapeStringVector)) { auto& firstVector = boost::any_cast(firstValue); const auto& secondVector = boost::any_cast(secondValue); firstVector.mVector.insert(firstVector.mVector.end(), secondVector.mVector.begin(), secondVector.mVector.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::processPaths(Files::PathContainer& dataDirs, bool create) { std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { path = it->string(); // Check if path contains a token if (!path.empty() && *path.begin() == '?') { std::string::size_type pos = path.find('?', 1); if (pos != std::string::npos && pos != 0) { TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); if (tokenIt != mTokensMapping.end()) { boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); if (pos < path.length() - 1) { // There is something after the token, so we should // append it to the path tempPath /= path.substr(pos + 1, path.length() - pos); } *it = tempPath; } else { // Clean invalid / unknown token, it will be removed outside the loop (*it).clear(); } } } if (!boost::filesystem::is_directory(*it)) { if (create) { try { boost::filesystem::create_directories (*it); } catch (...) {} if (boost::filesystem::is_directory(*it)) continue; } (*it).clear(); } } dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(), std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); } bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description) { 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 configFileStreamUnfiltered(cfgFile); boost::iostreams::filtering_istream configFileStream; configFileStream.push(escape_hash_filter()); configFileStream.push(configFileStreamUnfiltered); if (configFileStreamUnfiltered.is_open()) { boost::program_options::store(boost::program_options::parse_config_file( configFileStream, description, true), variables); return true; } else { if (!mSilent) Log(Debug::Error) << "Loading failed."; return false; } } return false; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const { return mFixedPath.getGlobalConfigPath(); } const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { return mFixedPath.getUserConfigPath(); } const boost::filesystem::path& ConfigurationManager::getUserDataPath() const { return mFixedPath.getUserDataPath(); } 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::getLogPath() const { return mLogPath; } const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const { return mScreenshotPath; } } /* namespace Cfg */ openmw-openmw-0.47.0/components/files/configurationmanager.hpp000066400000000000000000000047311413061077700246270ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include #include #include #include /** * \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, boost::program_options::options_description& description, bool quiet=false); boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, boost::program_options::options_description& description); void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, boost::program_options::options_description& description); void processPaths(Files::PathContainer& dataDirs, bool create = false); ///< \param create Try creating the directory, if it does not exist. /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; const boost::filesystem::path& getScreenshotPath() const; private: typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; bool loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description); void setupTokensMapping(); FixedPathType mFixedPath; boost::filesystem::path mLogPath; boost::filesystem::path mScreenshotPath; TokensMappingContainer mTokensMapping; bool mSilent; }; } /* namespace Cfg */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ openmw-openmw-0.47.0/components/files/constrainedfilestream.cpp000066400000000000000000000067221413061077700250070ustar00rootroot00000000000000#include "constrainedfilestream.hpp" #include #include #include "lowlevelfile.hpp" namespace { // somewhat arbitrary though 64KB buffers didn't seem to improve performance any const size_t sBufferSize = 8192; } namespace Files { class ConstrainedFileStreamBuf : public std::streambuf { size_t mOrigin; size_t mSize; LowLevelFile mFile; char mBuffer[sBufferSize]{0}; public: ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) { mFile.open (fname.c_str ()); mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; if (start != 0) mFile.seek(start); setg(nullptr,nullptr,nullptr); mOrigin = start; } int_type underflow() override { if(gptr() == egptr()) { size_t toRead = std::min((mOrigin+mSize)-(mFile.tell()), sBufferSize); // Read in the next chunk of data, and set the read pointers on success // Failure will throw exception in LowLevelFile size_t got = mFile.read(mBuffer, toRead); setg(&mBuffer[0], &mBuffer[0], &mBuffer[0]+got); } if(gptr() == egptr()) return traits_type::eof(); return traits_type::to_int_type(*gptr()); } pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override { 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 = (mFile.tell() - 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(); mFile.seek(mOrigin+newPos); // Clear read pointers so underflow() gets called on the next read attempt. setg(nullptr, nullptr, nullptr); return newPos; } pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override { if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) return traits_type::eof(); if ((size_t)pos > mSize) return traits_type::eof(); mFile.seek(mOrigin + pos); // Clear read pointers so underflow() gets called on the next read attempt. setg(nullptr, nullptr, nullptr); return pos; } }; ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr buf) : std::istream(buf.get()) , mBuf(std::move(buf)) { } IStreamPtr openConstrainedFileStream(const char *filename, size_t start, size_t length) { auto buf = std::unique_ptr(new ConstrainedFileStreamBuf(filename, start, length)); return IStreamPtr(new ConstrainedFileStream(std::move(buf))); } } openmw-openmw-0.47.0/components/files/constrainedfilestream.hpp000066400000000000000000000011731413061077700250070ustar00rootroot00000000000000#ifndef OPENMW_CONSTRAINEDFILESTREAM_H #define OPENMW_CONSTRAINEDFILESTREAM_H #include #include namespace Files { /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. class ConstrainedFileStream : public std::istream { public: ConstrainedFileStream(std::unique_ptr buf); virtual ~ConstrainedFileStream() {}; private: std::unique_ptr mBuf; }; typedef std::shared_ptr IStreamPtr; IStreamPtr openConstrainedFileStream(const char *filename, size_t start=0, size_t length=0xFFFFFFFF); } #endif openmw-openmw-0.47.0/components/files/escape.cpp000066400000000000000000000102461413061077700216560ustar00rootroot00000000000000#include "escape.hpp" #include namespace Files { const int escape_hash_filter::sEscape = '@'; const int escape_hash_filter::sEscapeIdentifier = 'a'; const int escape_hash_filter::sHashIdentifier = 'h'; escape_hash_filter::escape_hash_filter() : mSeenNonWhitespace(false), mFinishLine(false) { } escape_hash_filter::~escape_hash_filter() { } unescape_hash_filter::unescape_hash_filter() : expectingIdentifier(false) { } unescape_hash_filter::~unescape_hash_filter() { } std::string EscapeHashString::processString(const std::string & str) { std::string temp = str; static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier }; Misc::StringUtils::replaceAll(temp, hash, "#", 2, 1); static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier }; Misc::StringUtils::replaceAll(temp, escape, "@", 2, 1); return temp; } EscapeHashString::EscapeHashString() : mData() { } EscapeHashString::EscapeHashString(const std::string & str) : mData(EscapeHashString::processString(str)) { } EscapeHashString::EscapeHashString(const std::string & str, size_t pos, size_t len) : mData(EscapeHashString::processString(str), pos, len) { } EscapeHashString::EscapeHashString(const char * s) : mData(EscapeHashString::processString(std::string(s))) { } EscapeHashString::EscapeHashString(const char * s, size_t n) : mData(EscapeHashString::processString(std::string(s)), 0, n) { } EscapeHashString::EscapeHashString(size_t n, char c) : mData(n, c) { } template EscapeHashString::EscapeHashString(InputIterator first, InputIterator last) : mData(EscapeHashString::processString(std::string(first, last))) { } std::string EscapeHashString::toStdString() const { return std::string(mData); } std::istream & operator>> (std::istream & is, EscapeHashString & eHS) { std::string temp; is >> temp; eHS = EscapeHashString(temp); return is; } std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS) { os << eHS.mData; return os; } EscapeStringVector::EscapeStringVector() : mVector() { } EscapeStringVector::~EscapeStringVector() { } std::vector EscapeStringVector::toStdStringVector() const { std::vector temp = std::vector(); for (std::vector::const_iterator it = mVector.begin(); it != mVector.end(); ++it) { temp.push_back(it->toStdString()); } return temp; } // boost program options validation void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a) { boost::program_options::validators::check_first_occurrence(v); if (v.empty()) v = boost::any(EscapeHashString(boost::program_options::validators::get_single_string(tokens))); } void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int) { if (v.empty()) v = boost::any(EscapeStringVector()); EscapeStringVector * eSV = boost::any_cast(&v); for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) eSV->mVector.emplace_back(*it); } PathContainer EscapePath::toPathContainer(const EscapePathContainer & escapePathContainer) { PathContainer temp; for (EscapePathContainer::const_iterator it = escapePathContainer.begin(); it != escapePathContainer.end(); ++it) temp.push_back(it->mPath); return temp; } std::istream & operator>> (std::istream & istream, EscapePath & escapePath) { boost::iostreams::filtering_istream filteredStream; filteredStream.push(unescape_hash_filter()); filteredStream.push(istream); filteredStream >> escapePath.mPath; return istream; } } openmw-openmw-0.47.0/components/files/escape.hpp000066400000000000000000000125221413061077700216620ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_ESCAPE_HPP #define COMPONENTS_FILES_ESCAPE_HPP #include #include #include #include #include /** * \namespace Files */ namespace Files { /** * \struct escape_hash_filter */ struct escape_hash_filter : public boost::iostreams::input_filter { static const int sEscape; static const int sHashIdentifier; static const int sEscapeIdentifier; escape_hash_filter(); virtual ~escape_hash_filter(); template int get(Source & src); private: std::queue mNext; bool mSeenNonWhitespace; bool mFinishLine; }; template int escape_hash_filter::get(Source & src) { if (mNext.empty()) { int character = boost::iostreams::get(src); if (character == boost::iostreams::WOULD_BLOCK) { mNext.push(character); } else if (character == EOF) { mSeenNonWhitespace = false; mFinishLine = false; mNext.push(character); } else if (character == '\n') { mSeenNonWhitespace = false; mFinishLine = false; mNext.push(character); } else if (mFinishLine) { mNext.push(character); } else if (character == '#') { if (mSeenNonWhitespace) { mNext.push(sEscape); mNext.push(sHashIdentifier); } else { //it's fine being interpreted by Boost as a comment, and so is anything afterwards mNext.push(character); mFinishLine = true; } } else if (character == sEscape) { mNext.push(sEscape); mNext.push(sEscapeIdentifier); } else { mNext.push(character); } if (!mSeenNonWhitespace && !isspace(character)) mSeenNonWhitespace = true; } int retval = mNext.front(); mNext.pop(); return retval; } struct unescape_hash_filter : public boost::iostreams::input_filter { unescape_hash_filter(); virtual ~unescape_hash_filter(); template int get(Source & src); private: bool expectingIdentifier; }; template int unescape_hash_filter::get(Source & src) { int character; if (!expectingIdentifier) character = boost::iostreams::get(src); else { character = escape_hash_filter::sEscape; expectingIdentifier = false; } if (character == escape_hash_filter::sEscape) { int nextChar = boost::iostreams::get(src); int intended; if (nextChar == escape_hash_filter::sEscapeIdentifier) intended = escape_hash_filter::sEscape; else if (nextChar == escape_hash_filter::sHashIdentifier) intended = '#'; else if (nextChar == boost::iostreams::WOULD_BLOCK) { expectingIdentifier = true; intended = nextChar; } else intended = '?'; return intended; } else return character; } /** * \class EscapeHashString */ class EscapeHashString { private: std::string mData; public: static std::string processString(const std::string & str); EscapeHashString(); EscapeHashString(const std::string & str); EscapeHashString(const std::string & str, size_t pos, size_t len = std::string::npos); EscapeHashString(const char * s); EscapeHashString(const char * s, size_t n); EscapeHashString(size_t n, char c); template EscapeHashString(InputIterator first, InputIterator last); std::string toStdString() const; friend std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS); }; std::istream & operator>> (std::istream & is, EscapeHashString & eHS); struct EscapeStringVector { std::vector mVector; EscapeStringVector(); virtual ~EscapeStringVector(); std::vector toStdStringVector() const; }; //boost program options validation void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a); void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int); struct EscapePath { boost::filesystem::path mPath; static PathContainer toPathContainer(const std::vector & escapePathContainer); }; typedef std::vector EscapePathContainer; std::istream & operator>> (std::istream & istream, EscapePath & escapePath); } /* namespace Files */ #endif /* COMPONENTS_FILES_ESCAPE_HPP */ openmw-openmw-0.47.0/components/files/fixedpath.hpp000066400000000000000000000063411413061077700224000ustar00rootroot00000000000000#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.47.0/components/files/linuxpath.cpp000066400000000000000000000122101413061077700224230ustar00rootroot00000000000000#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("./"); std::string binPath(pathconf(".", _PC_PATH_MAX), '\0'); const char *statusPaths[] = {"/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file"}; for(const char *path : statusPaths) { if (readlink(path, &binPath[0], binPath.size()) != -1) { localPath = boost::filesystem::path(binPath).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.47.0/components/files/linuxpath.hpp000066400000000000000000000027241413061077700224410ustar00rootroot00000000000000#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.47.0/components/files/lowlevelfile.cpp000066400000000000000000000170411413061077700231070ustar00rootroot00000000000000#include "lowlevelfile.hpp" #include #include #include #if FILE_API == FILE_API_STDIO #include #include #endif #if FILE_API == FILE_API_POSIX #include #include #include #include #include #endif #if FILE_API == FILE_API_STDIO /* * * Implementation of LowLevelFile methods using c stdio * */ LowLevelFile::LowLevelFile () { mHandle = nullptr; } LowLevelFile::~LowLevelFile () { if (mHandle != nullptr) fclose (mHandle); } void LowLevelFile::open (char const * filename) { assert (mHandle == nullptr); mHandle = fopen (filename, "rb"); if (mHandle == nullptr) { std::ostringstream os; os << "Failed to open '" << filename << "' for reading: " << strerror(errno); throw std::runtime_error (os.str ()); } } void LowLevelFile::close () { assert (mHandle != nullptr); fclose (mHandle); mHandle = nullptr; } size_t LowLevelFile::size () { assert (mHandle != nullptr); long oldPosition = ftell (mHandle); if (oldPosition == -1) { std::ostringstream os; os << "An ftell() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } if (fseek (mHandle, 0, SEEK_END) != 0) { std::ostringstream os; os << "An fseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } long size = ftell (mHandle); if (size == -1) { std::ostringstream os; os << "An ftell() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } if (fseek (mHandle, oldPosition, SEEK_SET) != 0) { std::ostringstream os; os << "An fseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return size_t (size); } void LowLevelFile::seek (size_t position) { assert (mHandle != nullptr); if (fseek (mHandle, position, SEEK_SET) != 0) { std::ostringstream os; os << "An fseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } } size_t LowLevelFile::tell () { assert (mHandle != nullptr); long position = ftell (mHandle); if (position == -1) { std::ostringstream os; os << "An ftell() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return size_t (position); } size_t LowLevelFile::read (void * data, size_t size) { assert (mHandle != nullptr); int amount = fread (data, 1, size, mHandle); if (amount == 0 && ferror (mHandle)) { std::ostringstream os; os << "An attempt to read " << size << " bytes failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return amount; } #elif FILE_API == FILE_API_POSIX /* * * Implementation of LowLevelFile methods using posix IO calls * */ LowLevelFile::LowLevelFile () { mHandle = -1; } LowLevelFile::~LowLevelFile () { if (mHandle != -1) ::close (mHandle); } void LowLevelFile::open (char const * filename) { assert (mHandle == -1); #ifdef O_BINARY static const int openFlags = O_RDONLY | O_BINARY; #else static const int openFlags = O_RDONLY; #endif mHandle = ::open (filename, openFlags, 0); if (mHandle == -1) { std::ostringstream os; os << "Failed to open '" << filename << "' for reading: " << strerror(errno); throw std::runtime_error (os.str ()); } } void LowLevelFile::close () { assert (mHandle != -1); ::close (mHandle); mHandle = -1; } size_t LowLevelFile::size () { assert (mHandle != -1); size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); if (oldPosition == size_t (-1)) { std::ostringstream os; os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } size_t size = ::lseek (mHandle, 0, SEEK_END); if (size == size_t (-1)) { std::ostringstream os; os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } if (lseek (mHandle, oldPosition, SEEK_SET) == -1) { std::ostringstream os; os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return size; } void LowLevelFile::seek (size_t position) { assert (mHandle != -1); if (::lseek (mHandle, position, SEEK_SET) == -1) { std::ostringstream os; os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } } size_t LowLevelFile::tell () { assert (mHandle != -1); size_t position = ::lseek (mHandle, 0, SEEK_CUR); if (position == size_t (-1)) { std::ostringstream os; os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return position; } size_t LowLevelFile::read (void * data, size_t size) { assert (mHandle != -1); int amount = ::read (mHandle, data, size); if (amount == -1) { std::ostringstream os; os << "An attempt to read " << size << " bytes failed: " << strerror(errno); throw std::runtime_error (os.str ()); } return amount; } #elif FILE_API == FILE_API_WIN32 #include /* * * Implementation of LowLevelFile methods using Win32 API calls * */ LowLevelFile::LowLevelFile () { mHandle = INVALID_HANDLE_VALUE; } LowLevelFile::~LowLevelFile () { if (mHandle != INVALID_HANDLE_VALUE) CloseHandle (mHandle); } void LowLevelFile::open (char const * filename) { assert (mHandle == INVALID_HANDLE_VALUE); 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) { std::ostringstream os; os << "Failed to open '" << filename << "' for reading: " << GetLastError(); throw std::runtime_error (os.str ()); } mHandle = handle; } void LowLevelFile::close () { assert (mHandle != INVALID_HANDLE_VALUE); CloseHandle (mHandle); mHandle = INVALID_HANDLE_VALUE; } size_t LowLevelFile::size () { assert (mHandle != INVALID_HANDLE_VALUE); BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle (mHandle, &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; } void LowLevelFile::seek (size_t position) { assert (mHandle != INVALID_HANDLE_VALUE); if (SetFilePointer (mHandle, static_cast(position), nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) if (GetLastError () != NO_ERROR) throw std::runtime_error ("A seek operation on a file failed."); } size_t LowLevelFile::tell () { assert (mHandle != INVALID_HANDLE_VALUE); DWORD value = SetFilePointer (mHandle, 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 LowLevelFile::read (void * data, size_t size) { assert (mHandle != INVALID_HANDLE_VALUE); DWORD read; if (!ReadFile (mHandle, data, static_cast(size), &read, nullptr)) throw std::runtime_error ("A read operation on a file failed."); return read; } #endif openmw-openmw-0.47.0/components/files/lowlevelfile.hpp000066400000000000000000000017441413061077700231170ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_LOWLEVELFILE_HPP #define COMPONENTS_FILES_LOWLEVELFILE_HPP #include #define FILE_API_STDIO 0 #define FILE_API_POSIX 1 #define FILE_API_WIN32 2 #if defined(__linux) || defined(__unix) || defined(__posix) #define FILE_API FILE_API_POSIX #elif defined(_WIN32) #define FILE_API FILE_API_WIN32 #else #define FILE_API FILE_API_STDIO #endif #if FILE_API == FILE_API_STDIO #include #elif FILE_API == FILE_API_POSIX #elif FILE_API == FILE_API_WIN32 #include #else #error Unsupported File API #endif class LowLevelFile { public: LowLevelFile (); ~LowLevelFile (); void open (char const * filename); void close (); size_t size (); void seek (size_t Position); size_t tell (); size_t read (void * data, size_t size); private: #if FILE_API == FILE_API_STDIO FILE* mHandle; #elif FILE_API == FILE_API_POSIX int mHandle; #elif FILE_API == FILE_API_WIN32 HANDLE mHandle; #endif }; #endif openmw-openmw-0.47.0/components/files/macospath.cpp000066400000000000000000000104001413061077700223650ustar00rootroot00000000000000#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.47.0/components/files/macospath.hpp000066400000000000000000000027031413061077700224010ustar00rootroot00000000000000#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.47.0/components/files/memorystream.hpp000066400000000000000000000026171413061077700231520ustar00rootroot00000000000000#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.47.0/components/files/multidircollection.cpp000066400000000000000000000056011413061077700243220ustar00rootroot00000000000000#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; std::size_t len = left.length(); if (len!=right.length()) return false; for (std::size_t i=0; ifirst==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.47.0/components/files/multidircollection.hpp000066400000000000000000000050361413061077700243310ustar00rootroot00000000000000#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 leftr) return false; } return left.length() 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.47.0/components/files/windowspath.cpp000066400000000000000000000066741413061077700227770ustar00rootroot00000000000000#include "windowspath.hpp" #if defined(_WIN32) || defined(__WINDOWS__) #include #include #include #include #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.47.0/components/files/windowspath.hpp000066400000000000000000000033051413061077700227700ustar00rootroot00000000000000#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.47.0/components/fontloader/000077500000000000000000000000001413061077700207425ustar00rootroot00000000000000openmw-openmw-0.47.0/components/fontloader/fontloader.cpp000066400000000000000000000665751413061077700236260ustar00rootroot00000000000000#include "fontloader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { unsigned long utf8ToUnicode(const std::string& utf8) { 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; } // getUtf8, aka the worst function ever written. // This 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. std::string getUtf8 (unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) { // Hacks for polish font unsigned char win1250; std::map conv; conv[0x80] = 0xc6; conv[0x81] = 0x9c; conv[0x82] = 0xe6; conv[0x83] = 0xb3; conv[0x84] = 0xf1; conv[0x85] = 0xb9; conv[0x86] = 0xbf; conv[0x87] = 0x9f; conv[0x88] = 0xea; conv[0x89] = 0xea; conv[0x8a] = 0x0; // not contained in win1250 conv[0x8b] = 0x0; // not contained in win1250 conv[0x8c] = 0x8f; conv[0x8d] = 0xaf; conv[0x8e] = 0xa5; conv[0x8f] = 0x8c; conv[0x90] = 0xca; conv[0x93] = 0xa3; conv[0x94] = 0xf6; conv[0x95] = 0xf3; conv[0x96] = 0xaf; conv[0x97] = 0x8f; conv[0x99] = 0xd3; conv[0x9a] = 0xd1; conv[0x9c] = 0x0; // not contained in win1250 conv[0xa0] = 0xb9; conv[0xa1] = 0xaf; conv[0xa2] = 0xf3; conv[0xa3] = 0xbf; conv[0xa4] = 0x0; // not contained in win1250 conv[0xe1] = 0x8c; // Can't remember if this was supposed to read 0xe2, or is it just an extraneous copypaste? //conv[0xe1] = 0x8c; conv[0xe3] = 0x0; // not contained in win1250 conv[0xf5] = 0x0; // not contained in win1250 if (conv.find(c) != conv.end()) win1250 = conv[c]; else win1250 = c; return encoder.getUtf8(std::string(1, win1250)); } else return encoder.getUtf8(std::string(1, c)); } void fail (Files::IStreamPtr file, 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 << file->tellg(); throw std::runtime_error(error.str()); } } namespace Gui { FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, const std::string& userDataPath, float scalingFactor) : mVFS(vfs) , mUserDataPath(userDataPath) , mFontHeight(16) , mScalingFactor(scalingFactor) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; else mEncoding = encoding; int fontSize = Settings::Manager::getInt("font size", "GUI"); mFontHeight = std::min(std::max(12, fontSize), 20); MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = MyGUI::newDelegate(this, &FontLoader::loadFontFromXml); } FontLoader::~FontLoader() { try { MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the FontLoader destructor: " << e.what(); } for (std::vector::iterator it = mTextures.begin(); it != mTextures.end(); ++it) delete *it; mTextures.clear(); for (std::vector::iterator it = mFonts.begin(); it != mFonts.end(); ++it) { try { MyGUI::ResourceManager::getInstance().removeByName((*it)->getResourceName()); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } mFonts.clear(); } void FontLoader::loadBitmapFonts(bool exportToFile) { const std::map& index = mVFS->getIndex(); std::string pattern = "Fonts/"; mVFS->normalizeFilename(pattern); std::map::const_iterator found = index.lower_bound(pattern); while (found != index.end()) { const std::string& name = found->first; if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) loadBitmapFont(name, exportToFile); } else break; ++found; } } void FontLoader::loadTrueTypeFonts() { osgMyGUI::DataManager* dataManager = dynamic_cast(&osgMyGUI::DataManager::getInstance()); if (!dataManager) { Log(Debug::Error) << "Can not load TrueType fonts: osgMyGUI::DataManager is not available."; return; } const std::string cfg = dataManager->getDataPath(""); const std::string fontFile = mUserDataPath + "/" + "Fonts" + "/" + "openmw_font.xml"; if (!boost::filesystem::exists(fontFile)) return; dataManager->setResourcePath(mUserDataPath + "/" + "Fonts"); MyGUI::ResourceManager::getInstance().load("openmw_font.xml"); dataManager->setResourcePath(cfg); } 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, bool exportToFile) { Files::IStreamPtr file = mVFS->get(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(); std::string resourceName; if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic")) resourceName = "Magic Cards"; else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century")) resourceName = "Century Gothic"; else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric")) resourceName = "Daedric"; if (exportToFile) { osg::ref_ptr image = new osg::Image; image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); assert (image->isDataContiguous()); memcpy(image->data(), &textureData[0], textureData.size()); Log(Debug::Info) << "Writing " << resourceName + ".png"; osgDB::writeImageFile(*image, resourceName + ".png"); } // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); mFonts.push_back(font); 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(); // Using ResourceManualFont::setTexture, enable for MyGUI 3.2.3 /* osg::ref_ptr texture = new osg::Texture2D; texture->setImage(image); osgMyGUI::OSGTexture* myguiTex = new osgMyGUI::OSGTexture(texture); mTextures.push_back(myguiTex); font->setTexture(myguiTex); */ // 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", resourceName); 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 = utf8ToUnicode(getUtf8(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"); } if (exportToFile) { Log(Debug::Info) << "Writing " << resourceName + ".xml"; xmlDocument.createDeclaration(); xmlDocument.save(resourceName + ".xml"); } font->deserialization(root, MyGUI::Version(3,2,0)); // Setup "book" version of font as fallback if we will not use TrueType fonts MyGUI::ResourceManualFont* bookFont = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); mFonts.push_back(bookFont); bookFont->deserialization(root, MyGUI::Version(3,2,0)); bookFont->setResourceName("Journalbook " + resourceName); // Remove automatically registered fonts for (std::vector::iterator it = mFonts.begin(); it != mFonts.end();) { if ((*it)->getResourceName() == font->getResourceName()) { MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); it = mFonts.erase(it); } else if ((*it)->getResourceName() == bookFont->getResourceName()) { MyGUI::ResourceManager::getInstance().removeByName(bookFont->getResourceName()); it = mFonts.erase(it); } else ++it; } MyGUI::ResourceManager::getInstance().addResource(font); MyGUI::ResourceManager::getInstance().addResource(bookFont); } void FontLoader::loadFontFromXml(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) { MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); bool createCopy = false; while (resourceNode.next("Resource")) { std::string type, name; resourceNode->findAttribute("type", type); resourceNode->findAttribute("name", name); if (name.empty()) continue; if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) { createCopy = true; // For TrueType fonts we should override Size and Resolution properties // to allow to configure font size via config file, without need to edit XML files. // Also we should take UI scaling factor in account. int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); resolution = std::min(960, std::max(48, resolution)) * mScalingFactor; MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); resolutionNode->addAttribute("value", std::to_string(resolution)); MyGUI::xml::ElementPtr sizeNode = resourceNode->createChild("Property"); sizeNode->addAttribute("key", "Size"); sizeNode->addAttribute("value", std::to_string(mFontHeight)); } else 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); if (createCopy) { std::unique_ptr copy{_node->createCopy()}; MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); while (copyFont.next("Resource")) { std::string type, name; copyFont->findAttribute("type", type); copyFont->findAttribute("name", name); if (name.empty()) continue; if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) { // Since the journal and books use the custom scaling factor depending on resolution, // setup separate fonts with different Resolution to fit these windows. // These fonts have an internal prefix. int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); resolution = std::min(960, std::max(48, resolution)); float currentX = Settings::Manager::getInt("resolution x", "Video"); float currentY = Settings::Manager::getInt("resolution y", "Video"); // TODO: read size from openmw_layout.xml somehow float heightScale = (currentY / 520); float widthScale = (currentX / 600); float uiScale = std::min(widthScale, heightScale); resolution *= uiScale; MyGUI::xml::ElementPtr resolutionNode = copyFont->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); resolutionNode->addAttribute("value", std::to_string(resolution)); copyFont->setAttribute("name", "Journalbook " + name); } } MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy.get(), _file, _version); } } int FontLoader::getFontHeight() { return mFontHeight; } } openmw-openmw-0.47.0/components/fontloader/fontloader.hpp000066400000000000000000000032031413061077700236060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FONTLOADER_H #define OPENMW_COMPONENTS_FONTLOADER_H #include "boost/filesystem/operations.hpp" #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, const std::string& userDataPath, float scalingFactor); ~FontLoader(); /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? void loadBitmapFonts (bool exportToFile); void loadTrueTypeFonts (); void loadFontFromXml(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); int getFontHeight(); private: ToUTF8::FromType mEncoding; const VFS::Manager* mVFS; std::string mUserDataPath; int mFontHeight; float mScalingFactor; std::vector mTextures; std::vector mFonts; /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? void loadBitmapFont (const std::string& fileName, bool exportToFile); FontLoader(const FontLoader&); void operator=(const FontLoader&); }; } #endif openmw-openmw-0.47.0/components/interpreter/000077500000000000000000000000001413061077700211505ustar00rootroot00000000000000openmw-openmw-0.47.0/components/interpreter/context.hpp000066400000000000000000000060611413061077700233500ustar00rootroot00000000000000#ifndef INTERPRETER_CONTEXT_H_INCLUDED #define INTERPRETER_CONTEXT_H_INCLUDED #include #include namespace Interpreter { class Context { public: virtual ~Context() {} 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 (const std::string& name) const = 0; virtual int getGlobalLong (const std::string& name) const = 0; virtual float getGlobalFloat (const std::string& name) const = 0; virtual void setGlobalShort (const std::string& name, int value) = 0; virtual void setGlobalLong (const std::string& name, int value) = 0; virtual void setGlobalFloat (const std::string& name, float value) = 0; virtual std::vector getGlobals () const = 0; virtual char getGlobalType (const std::string& name) const = 0; virtual std::string getActionBinding(const std::string& 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 (const std::string& id, const std::string& name, bool global) const = 0; virtual int getMemberLong (const std::string& id, const std::string& name, bool global) const = 0; virtual float getMemberFloat (const std::string& id, const std::string& name, bool global) const = 0; virtual void setMemberShort (const std::string& id, const std::string& name, int value, bool global) = 0; virtual void setMemberLong (const std::string& id, const std::string& name, int value, bool global) = 0; virtual void setMemberFloat (const std::string& id, const std::string& name, float value, bool global) = 0; }; } #endif openmw-openmw-0.47.0/components/interpreter/controlopcodes.hpp000066400000000000000000000032051413061077700247160ustar00rootroot00000000000000#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.47.0/components/interpreter/defines.cpp000066400000000000000000000242401413061077700232730ustar00rootroot00000000000000#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(); } std::string fixDefinesReal(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 = text.substr(i+1, globals[j].length()); transform(temp.begin(), temp.end(), temp.begin(), ::tolower); } 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.47.0/components/interpreter/defines.hpp000066400000000000000000000005771413061077700233070ustar00rootroot00000000000000#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.47.0/components/interpreter/docs/000077500000000000000000000000001413061077700221005ustar00rootroot00000000000000openmw-openmw-0.47.0/components/interpreter/docs/vmformat.txt000066400000000000000000000135531413061077700245030ustar00rootroot00000000000000Note: 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 op 19: take square root of stack[0] (float) 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.47.0/components/interpreter/genericopcodes.hpp000066400000000000000000000046471413061077700246650ustar00rootroot00000000000000#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.47.0/components/interpreter/installopcodes.cpp000066400000000000000000000116361413061077700247060ustar00rootroot00000000000000#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, new OpPushInt); interpreter.installSegment5 (3, new OpIntToFloat); interpreter.installSegment5 (6, new OpFloatToInt); interpreter.installSegment5 (7, new OpNegateInt); interpreter.installSegment5 (8, new OpNegateFloat); interpreter.installSegment5 (17, new OpIntToFloat1); interpreter.installSegment5 (18, new OpFloatToInt1); // local variables, global variables & literals interpreter.installSegment5 (0, new OpStoreLocalShort); interpreter.installSegment5 (1, new OpStoreLocalLong); interpreter.installSegment5 (2, new OpStoreLocalFloat); interpreter.installSegment5 (4, new OpFetchIntLiteral); interpreter.installSegment5 (5, new OpFetchFloatLiteral); interpreter.installSegment5 (21, new OpFetchLocalShort); interpreter.installSegment5 (22, new OpFetchLocalLong); interpreter.installSegment5 (23, new OpFetchLocalFloat); interpreter.installSegment5 (39, new OpStoreGlobalShort); interpreter.installSegment5 (40, new OpStoreGlobalLong); interpreter.installSegment5 (41, new OpStoreGlobalFloat); interpreter.installSegment5 (42, new OpFetchGlobalShort); interpreter.installSegment5 (43, new OpFetchGlobalLong); interpreter.installSegment5 (44, new OpFetchGlobalFloat); interpreter.installSegment5 (59, new OpStoreMemberShort (false)); interpreter.installSegment5 (60, new OpStoreMemberLong (false)); interpreter.installSegment5 (61, new OpStoreMemberFloat (false)); interpreter.installSegment5 (62, new OpFetchMemberShort (false)); interpreter.installSegment5 (63, new OpFetchMemberLong (false)); interpreter.installSegment5 (64, new OpFetchMemberFloat (false)); interpreter.installSegment5 (65, new OpStoreMemberShort (true)); interpreter.installSegment5 (66, new OpStoreMemberLong (true)); interpreter.installSegment5 (67, new OpStoreMemberFloat (true)); interpreter.installSegment5 (68, new OpFetchMemberShort (true)); interpreter.installSegment5 (69, new OpFetchMemberLong (true)); interpreter.installSegment5 (70, new OpFetchMemberFloat (true)); // math interpreter.installSegment5 (9, new OpAddInt); interpreter.installSegment5 (10, new OpAddInt); interpreter.installSegment5 (11, new OpSubInt); interpreter.installSegment5 (12, new OpSubInt); interpreter.installSegment5 (13, new OpMulInt); interpreter.installSegment5 (14, new OpMulInt); interpreter.installSegment5 (15, new OpDivInt); interpreter.installSegment5 (16, new OpDivInt); interpreter.installSegment5 (19, new OpSquareRoot); interpreter.installSegment5 (26, new OpCompare >); interpreter.installSegment5 (27, new OpCompare >); interpreter.installSegment5 (28, new OpCompare >); interpreter.installSegment5 (29, new OpCompare >); interpreter.installSegment5 (30, new OpCompare >); interpreter.installSegment5 (31, new OpCompare >); interpreter.installSegment5 (32, new OpCompare >); interpreter.installSegment5 (33, new OpCompare >); interpreter.installSegment5 (34, new OpCompare >); interpreter.installSegment5 (35, new OpCompare >); interpreter.installSegment5 (36, new OpCompare >); interpreter.installSegment5 (37, new OpCompare >); // control structures interpreter.installSegment5 (20, new OpReturn); interpreter.installSegment5 (24, new OpSkipZero); interpreter.installSegment5 (25, new OpSkipNonZero); interpreter.installSegment0 (1, new OpJumpForward); interpreter.installSegment0 (2, new OpJumpBackward); // misc interpreter.installSegment3 (0, new OpMessageBox); interpreter.installSegment5 (58, new OpReport); } } openmw-openmw-0.47.0/components/interpreter/installopcodes.hpp000066400000000000000000000003171413061077700247050ustar00rootroot00000000000000#ifndef INTERPRETER_INSTALLOPCODES_H_INCLUDED #define INTERPRETER_INSTALLOPCODES_H_INCLUDED namespace Interpreter { class Interpreter; void installOpcodes (Interpreter& interpreter); } #endif openmw-openmw-0.47.0/components/interpreter/interpreter.cpp000066400000000000000000000116761413061077700242320ustar00rootroot00000000000000#include "interpreter.hpp" #include #include #include "opcodes.hpp" namespace Interpreter { void Interpreter::execute (Type_Code code) { unsigned int segSpec = code>>30; switch (segSpec) { case 0: { int opcode = code>>24; unsigned int arg0 = code & 0xffffff; std::map::iterator iter = mSegment0.find (opcode); if (iter==mSegment0.end()) abortUnknownCode (0, opcode); iter->second->execute (mRuntime, arg0); return; } case 2: { int opcode = (code>>20) & 0x3ff; unsigned int arg0 = code & 0xfffff; std::map::iterator iter = mSegment2.find (opcode); if (iter==mSegment2.end()) abortUnknownCode (2, opcode); iter->second->execute (mRuntime, arg0); return; } } segSpec = code>>26; switch (segSpec) { case 0x30: { int opcode = (code>>8) & 0x3ffff; unsigned int arg0 = code & 0xff; std::map::iterator iter = mSegment3.find (opcode); if (iter==mSegment3.end()) abortUnknownCode (3, opcode); iter->second->execute (mRuntime, arg0); return; } case 0x32: { int opcode = code & 0x3ffffff; std::map::iterator iter = mSegment5.find (opcode); if (iter==mSegment5.end()) abortUnknownCode (5, opcode); iter->second->execute (mRuntime); return; } } abortUnknownSegment (code); } void Interpreter::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); } void Interpreter::abortUnknownSegment (Type_Code code) { const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code); throw std::runtime_error (error); } 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) {} Interpreter::~Interpreter() { for (std::map::iterator iter (mSegment0.begin()); iter!=mSegment0.end(); ++iter) delete iter->second; for (std::map::iterator iter (mSegment2.begin()); iter!=mSegment2.end(); ++iter) delete iter->second; for (std::map::iterator iter (mSegment3.begin()); iter!=mSegment3.end(); ++iter) delete iter->second; for (std::map::iterator iter (mSegment5.begin()); iter!=mSegment5.end(); ++iter) delete iter->second; } void Interpreter::installSegment0 (int code, Opcode1 *opcode) { assert(mSegment0.find(code) == mSegment0.end()); mSegment0.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment2 (int code, Opcode1 *opcode) { assert(mSegment2.find(code) == mSegment2.end()); mSegment2.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment3 (int code, Opcode1 *opcode) { assert(mSegment3.find(code) == mSegment3.end()); mSegment3.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment5 (int code, Opcode0 *opcode) { assert(mSegment5.find(code) == mSegment5.end()); mSegment5.insert (std::make_pair (code, opcode)); } 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 "runtime.hpp" #include "types.hpp" namespace Interpreter { class Opcode0; class Opcode1; 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 abortUnknownCode (int segment, int opcode); void abortUnknownSegment (Type_Code code); void begin(); void end(); public: Interpreter(); ~Interpreter(); void installSegment0 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. void installSegment2 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. void installSegment3 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. void installSegment5 (int code, Opcode0 *opcode); ///< ownership of \a opcode is transferred to *this. void run (const Type_Code *code, int codeSize, Context& context); }; } #endif openmw-openmw-0.47.0/components/interpreter/localopcodes.hpp000066400000000000000000000227131413061077700243350ustar00rootroot00000000000000#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 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 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 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 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 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 name = runtime.getStringLiteral (index); Type_Float value = runtime.getContext().getGlobalFloat (name); runtime[0].mFloat = value; } }; class OpStoreMemberShort : public Opcode0 { bool mGlobal; public: OpStoreMemberShort (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; Type_Integer index = runtime[1].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.getContext().setMemberShort (id, variable, data, mGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; class OpStoreMemberLong : public Opcode0 { bool mGlobal; public: OpStoreMemberLong (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; Type_Integer index = runtime[1].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.getContext().setMemberLong (id, variable, data, mGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; class OpStoreMemberFloat : public Opcode0 { bool mGlobal; public: OpStoreMemberFloat (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; Type_Integer index = runtime[1].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.getContext().setMemberFloat (id, variable, data, mGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; class OpFetchMemberShort : public Opcode0 { bool mGlobal; public: OpFetchMemberShort (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.pop(); int value = runtime.getContext().getMemberShort (id, variable, mGlobal); runtime[0].mInteger = value; } }; class OpFetchMemberLong : public Opcode0 { bool mGlobal; public: OpFetchMemberLong (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.pop(); int value = runtime.getContext().getMemberLong (id, variable, mGlobal); runtime[0].mInteger = value; } }; class OpFetchMemberFloat : public Opcode0 { bool mGlobal; public: OpFetchMemberFloat (bool global) : mGlobal (global) {} void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string variable = runtime.getStringLiteral (index); runtime.pop(); float value = runtime.getContext().getMemberFloat (id, variable, mGlobal); runtime[0].mFloat = value; } }; } #endif openmw-openmw-0.47.0/components/interpreter/mathopcodes.hpp000066400000000000000000000053511413061077700241730ustar00rootroot00000000000000#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; } }; class OpSquareRoot : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float value = runtime[0].mFloat; if (value<0) throw std::runtime_error ( "square root of negative number (we aren't that imaginary)"); value = std::sqrt (value); runtime[0].mFloat = value; } }; 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.47.0/components/interpreter/miscopcodes.hpp000066400000000000000000000125331413061077700241750ustar00rootroot00000000000000#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(const std::string& message) override { mFormattedMessage.clear(); MessageFormatParser::process(message); } std::string getFormattedMessage() const { return mFormattedMessage; } }; inline std::string formatMessage (const std::string& 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 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 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]); int offset = 0; for (; index; --index) { offset += static_cast(std::strlen (literalBlock+offset)) + 1; if (offset / 4 >= static_cast (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.resize (mStack.size()-1); } 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.47.0/components/interpreter/runtime.hpp000066400000000000000000000027421413061077700233510ustar00rootroot00000000000000#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 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.47.0/components/interpreter/types.hpp000066400000000000000000000014351413061077700230300ustar00rootroot00000000000000#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.47.0/components/loadinglistener/000077500000000000000000000000001413061077700217705ustar00rootroot00000000000000openmw-openmw-0.47.0/components/loadinglistener/loadinglistener.hpp000066400000000000000000000034611413061077700256700ustar00rootroot00000000000000#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.47.0/components/misc/000077500000000000000000000000001413061077700175405ustar00rootroot00000000000000openmw-openmw-0.47.0/components/misc/algorithm.hpp000066400000000000000000000016101413061077700222350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_ALGORITHM_H #define OPENMW_COMPONENTS_MISC_ALGORITHM_H #include #include 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; } } #endif openmw-openmw-0.47.0/components/misc/barrier.hpp000066400000000000000000000026011413061077700216760ustar00rootroot00000000000000#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(int 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 int currentGeneration = mGeneration; if (mRendezvousCount == mThreadCount) { ++mGeneration; mRendezvousCount = 0; func(); mRendezvous.notify_all(); } else { mRendezvous.wait(lock, [&]() { return mGeneration != currentGeneration; }); } } private: int mThreadCount; int mRendezvousCount; int mGeneration; mutable std::mutex mMutex; std::condition_variable mRendezvous; }; } #endif openmw-openmw-0.47.0/components/misc/budgetmeasurement.hpp000066400000000000000000000021501413061077700237670ustar00rootroot00000000000000#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.47.0/components/misc/constants.hpp000066400000000000000000000021351413061077700222660ustar00rootroot00000000000000#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; } #endif openmw-openmw-0.47.0/components/misc/convert.hpp000066400000000000000000000023121413061077700217270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H #include #include #include #include #include #include namespace Misc { namespace Convert { inline osg::Vec3f makeOsgVec3f(const float* values) { return osg::Vec3f(values[0], values[1], values[2]); } inline osg::Vec3f makeOsgVec3f(const btVector3& value) { return osg::Vec3f(value.x(), value.y(), value.z()); } 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()); } } } #endifopenmw-openmw-0.47.0/components/misc/coordinateconverter.hpp000066400000000000000000000040421413061077700243300ustar00rootroot00000000000000#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.47.0/components/misc/endianness.hpp000066400000000000000000000052621413061077700224050ustar00rootroot00000000000000#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.47.0/components/misc/frameratelimiter.hpp000066400000000000000000000037471413061077700236200ustar00rootroot00000000000000#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.47.0/components/misc/guarded.hpp000066400000000000000000000042551413061077700216720ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_GUARDED_H #define OPENMW_COMPONENTS_MISC_GUARDED_H #include #include #include namespace Misc { template class Locked { public: Locked(std::mutex& mutex, T& value) : mLock(mutex), mValue(value) {} T& get() const { return mValue.get(); } T* operator ->() const { return std::addressof(get()); } 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.47.0/components/misc/hash.hpp000066400000000000000000000004651413061077700212010ustar00rootroot00000000000000#ifndef MISC_HASH_H #define MISC_HASH_H namespace Misc { /// Implemented similar to the boost::hash_combine template inline void hashCombine(std::size_t& seed, const T& v) { std::hash hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); } } #endifopenmw-openmw-0.47.0/components/misc/helpviewer.cpp000066400000000000000000000003701413061077700224160ustar00rootroot00000000000000#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.47.0/components/misc/helpviewer.hpp000066400000000000000000000001521413061077700224210ustar00rootroot00000000000000#pragma once namespace Misc { namespace HelpViewer { void openHelp(const char* url); } } openmw-openmw-0.47.0/components/misc/mathutil.hpp000066400000000000000000000012541413061077700221020ustar00rootroot00000000000000#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.47.0/components/misc/messageformatparser.cpp000066400000000000000000000053651413061077700243270ustar00rootroot00000000000000#include "messageformatparser.hpp" namespace Misc { MessageFormatParser::~MessageFormatParser() {} void MessageFormatParser::process(const std::string& 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.47.0/components/misc/messageformatparser.hpp000066400000000000000000000015161413061077700243260ustar00rootroot00000000000000#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(const std::string& message); }; } #endif openmw-openmw-0.47.0/components/misc/objectpool.hpp000066400000000000000000000036101413061077700224110ustar00rootroot00000000000000#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.47.0/components/misc/resourcehelpers.cpp000066400000000000000000000105031413061077700234550ustar00rootroot00000000000000#include "resourcehelpers.hpp" #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 Misc::ResourceHelpers::changeExtensionToDds(std::string &path) { std::string::size_type pos = path.rfind('.'); if(pos != std::string::npos && path.compare(pos, path.length() - pos, ".dds") != 0) { path.replace(pos, path.length(), ".dds"); return true; } return false; } 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; } openmw-openmw-0.47.0/components/misc/resourcehelpers.hpp000066400000000000000000000022361413061077700234660ustar00rootroot00000000000000#ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H #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); } } #endif openmw-openmw-0.47.0/components/misc/rng.cpp000066400000000000000000000022531413061077700210340ustar00rootroot00000000000000#include "rng.hpp" #include #include namespace { Misc::Rng::Seed sSeed; } namespace Misc { Rng::Seed::Seed() {} Rng::Seed::Seed(unsigned int seed) { mGenerator.seed(seed); } Rng::Seed& Rng::getSeed() { return sSeed; } void Rng::init(unsigned int seed) { sSeed.mGenerator.seed(seed); } float Rng::rollProbability(Seed& seed) { return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(seed.mGenerator); } float Rng::rollClosedProbability(Seed& seed) { return std::uniform_real_distribution(0, 1)(seed.mGenerator); } int Rng::rollDice(int max, Seed& seed) { return max > 0 ? std::uniform_int_distribution(0, max - 1)(seed.mGenerator) : 0; } unsigned int Rng::generateDefaultSeed() { return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } float Rng::deviate(float mean, float deviation, Seed& seed) { return std::uniform_real_distribution(mean - deviation, mean + deviation)(seed.mGenerator); } } openmw-openmw-0.47.0/components/misc/rng.hpp000066400000000000000000000022601413061077700210370ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_RNG_H #define OPENMW_COMPONENTS_MISC_RNG_H #include #include namespace Misc { /* Provides central implementation of the RNG logic */ class Rng { public: class Seed { std::mt19937 mGenerator; public: Seed(); Seed(const Seed&) = delete; Seed(unsigned int seed); friend class Rng; }; static Seed& getSeed(); /// seed the RNG static void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. static float rollProbability(Seed& seed = getSeed()); /// return value in range [0.0f, 1.0f] <- note closed upper range. static float rollClosedProbability(Seed& seed = getSeed()); /// return value in range [0, max) <- note open upper range. static int rollDice(int max, Seed& seed = getSeed()); /// return value in range [0, 99] static int roll0to99(Seed& seed = getSeed()) { return rollDice(100, seed); } /// returns default seed for RNG static unsigned int generateDefaultSeed(); static float deviate(float mean, float deviation, Seed& seed = getSeed()); }; } #endif openmw-openmw-0.47.0/components/misc/stringops.hpp000066400000000000000000000221031413061077700222770ustar00rootroot00000000000000#ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H #include #include #include #include "utf8stream.hpp" 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 { 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 char toLower(char c) { return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; } static Utf8Stream::UnicodeChar toLowerUtf8(Utf8Stream::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; // Deutch 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 ()) { Utf8Stream::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; } static bool ciLess(const std::string &x, const std::string &y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } static bool ciEqual(const std::string &x, const std::string &y) { if (x.size() != y.size()) { return false; } std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { if (toLower(*xit) != toLower(*yit)) { return false; } } return true; } static int ciCompareLen(const std::string &x, const std::string &y, size_t len) { std::string::const_iterator xit = x.begin(); std::string::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 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; } /** @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. * @param whatLen The length of the string to replace. * @param withLen The length of the replacement string. * * @return A reference to the string passed in @p str. */ static std::string &replaceAll(std::string &str, const char *what, const char *with, std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) { if (whatLen == std::string::npos) whatLen = strlen(what); if (withLen == std::string::npos) withLen = strlen(with); std::size_t found; std::size_t offset = 0; while((found = str.find(what, offset, whatLen)) != std::string::npos) { str.replace(found, whatLen, with, withLen); offset = found + withLen; } 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) { // left trim s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); // right trim s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); } template static inline void split(const std::string& str, Container& cont, const std::string& delims = " ") { std::size_t current, previous = 0; current = str.find_first_of(delims); while (current != std::string::npos) { cont.push_back(str.substr(previous, current - previous)); previous = current + 1; current = str.find_first_of(delims, previous); } cont.push_back(str.substr(previous, current - previous)); } // TODO: use the std::string_view once we will use the C++17. // It should allow us to avoid data copying while we still will support both string and literal arguments. static inline void replaceAll(std::string& data, std::string toSearch, std::string replaceStr) { size_t pos = data.find(toSearch); while( pos != std::string::npos) { data.replace(pos, toSearch.size(), replaceStr); pos = data.find(toSearch, pos + replaceStr.size()); } } static inline void replaceLast(std::string& str, std::string substr, std::string with) { size_t pos = str.rfind(substr); if (pos == std::string::npos) return; str.replace(pos, substr.size(), with); } }; } #endif openmw-openmw-0.47.0/components/misc/thread.cpp000066400000000000000000000034601413061077700215160ustar00rootroot00000000000000#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) #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #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.47.0/components/misc/thread.hpp000066400000000000000000000002501413061077700215150ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_THREAD_H #define OPENMW_COMPONENTS_MISC_THREAD_H #include namespace Misc { void setCurrentThreadIdlePriority(); } #endif openmw-openmw-0.47.0/components/misc/timer.hpp000066400000000000000000000017211413061077700213720ustar00rootroot00000000000000#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) { if (mTimeLeft > 0) { mTimeLeft -= duration; return TimerStatus::Waiting; } mTimeLeft = Rng::deviate(mPeriod, mDeviation); return TimerStatus::Elapsed; } void reset(float timeLeft) { mTimeLeft = timeLeft; } private: const float mPeriod; const float mDeviation; float mTimeLeft; }; } #endif openmw-openmw-0.47.0/components/misc/utf8stream.hpp000066400000000000000000000050021413061077700223500ustar00rootroot00000000000000#ifndef MISC_UTF8ITER_HPP #define MISC_UTF8ITER_HPP #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 ((unsigned char*) str), nxt ((unsigned char*) str), end ((unsigned char*) str + strlen(str)), val(Utf8Stream::sBadChar()) { } Utf8Stream (std::pair range) : cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { } 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); } 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.47.0/components/misc/weakcache.hpp000066400000000000000000000072761413061077700222000ustar00rootroot00000000000000#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); bool operator!=(const iterator& other); 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) { return mCurrent == other.mCurrent; } template bool WeakCache::iterator::operator!=(const iterator& other) { 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.47.0/components/myguiplatform/000077500000000000000000000000001413061077700215045ustar00rootroot00000000000000openmw-openmw-0.47.0/components/myguiplatform/additivelayer.cpp000066400000000000000000000014741413061077700250440ustar00rootroot00000000000000#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.47.0/components/myguiplatform/additivelayer.hpp000066400000000000000000000011751413061077700250470ustar00rootroot00000000000000#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.47.0/components/myguiplatform/myguicompat.h000066400000000000000000000005431413061077700242150ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H #include #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const #else #define OPENMW_MYGUI_CONST_GETTER_3_4_1 #endif #endif // OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H openmw-openmw-0.47.0/components/myguiplatform/myguidatamanager.cpp000066400000000000000000000031501413061077700255260ustar00rootroot00000000000000#include "myguidatamanager.hpp" #include #include #include #include namespace osgMyGUI { void DataManager::setResourcePath(const std::string &path) { mResourcePath = path; } MyGUI::IDataStream *DataManager::getData(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { std::string fullpath = getDataPath(name); std::unique_ptr stream; stream.reset(new boost::filesystem::ifstream); stream->open(fullpath, std::ios::binary); if (stream->fail()) { Log(Debug::Error) << "DataManager::getData: Failed to open '" << name << "'"; return nullptr; } return new MyGUI::DataFileStream(stream.release()); } void DataManager::freeData(MyGUI::IDataStream *data) { delete data; } bool DataManager::isDataExist(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { std::string fullpath = mResourcePath + "/" + name; return boost::filesystem::exists(fullpath); } const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 { // TODO: pattern matching (unused?) static MyGUI::VectorString strings; strings.clear(); strings.push_back(getDataPath(pattern)); return strings; } const std::string &DataManager::getDataPath(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { static std::string result; result.clear(); if (!isDataExist(name)) { return result; } result = mResourcePath + "/" + name; return result; } } openmw-openmw-0.47.0/components/myguiplatform/myguidatamanager.hpp000066400000000000000000000026031413061077700255350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H #include #include "myguicompat.h" namespace osgMyGUI { class DataManager : public MyGUI::DataManager { public: void initialise() {} void shutdown() {} 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) OPENMW_MYGUI_CONST_GETTER_3_4_1 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) OPENMW_MYGUI_CONST_GETTER_3_4_1 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) OPENMW_MYGUI_CONST_GETTER_3_4_1 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) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; private: std::string mResourcePath; }; } #endif openmw-openmw-0.47.0/components/myguiplatform/myguiloglistener.cpp000066400000000000000000000024211413061077700256110ustar00rootroot00000000000000#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.47.0/components/myguiplatform/myguiloglistener.hpp000066400000000000000000000036341413061077700256250ustar00rootroot00000000000000#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.47.0/components/myguiplatform/myguiplatform.cpp000066400000000000000000000026401413061077700251110ustar00rootroot00000000000000#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, float uiScalingFactor) : mRenderManager(nullptr) , mDataManager(nullptr) , mLogManager(nullptr) , mLogFacility(nullptr) { mLogManager = new MyGUI::LogManager(); mRenderManager = new RenderManager(viewer, guiRoot, imageManager, uiScalingFactor); mDataManager = new DataManager(); } Platform::~Platform() { delete mRenderManager; mRenderManager = nullptr; delete mDataManager; mDataManager = nullptr; delete mLogManager; mLogManager = nullptr; delete mLogFacility; mLogFacility = nullptr; } void Platform::initialise(const std::string &resourcePath, const std::string &_logName) { if (!_logName.empty() && !mLogFacility) { mLogFacility = new LogFacility(_logName, false); mLogManager->addLogSource(mLogFacility->getSource()); } mDataManager->setResourcePath(resourcePath); mRenderManager->initialise(); mDataManager->initialise(); } void Platform::shutdown() { mRenderManager->shutdown(); mDataManager->shutdown(); } RenderManager *Platform::getRenderManagerPtr() { return mRenderManager; } DataManager *Platform::getDataManagerPtr() { return mDataManager; } } openmw-openmw-0.47.0/components/myguiplatform/myguiplatform.hpp000066400000000000000000000020371413061077700251160ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H #include namespace osgViewer { class Viewer; } namespace osg { class Group; } namespace Resource { class ImageManager; } namespace MyGUI { class LogManager; } namespace osgMyGUI { class RenderManager; class DataManager; class LogFacility; class Platform { public: Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* imageManager, float uiScalingFactor); ~Platform(); void initialise(const std::string& resourcePath, const std::string& _logName = "MyGUI.log"); void shutdown(); RenderManager* getRenderManagerPtr(); DataManager* getDataManagerPtr(); private: RenderManager* mRenderManager; DataManager* mDataManager; MyGUI::LogManager* mLogManager; LogFacility* mLogFacility; void operator=(const Platform&); Platform(const Platform&); }; } #endif openmw-openmw-0.47.0/components/myguiplatform/myguirendermanager.cpp000066400000000000000000000403651413061077700261050ustar00rootroot00000000000000#include "myguirendermanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "myguicompat.h" #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 osg::Drawable::UpdateCallback { public: FrameUpdate() : mRenderManager(nullptr) { } void setRenderManager(osgMyGUI::RenderManager* renderManager) { mRenderManager = renderManager; } void update(osg::NodeVisitor*, osg::Drawable*) override { if (mRenderManager) mRenderManager->update(); } private: osgMyGUI::RenderManager* mRenderManager; }; // Stage 1: collect draw calls. Run during the Cull traversal. class CollectDrawCalls : public osg::Drawable::CullCallback { public: CollectDrawCalls() : mRenderManager(nullptr) { } void setRenderManager(osgMyGUI::RenderManager* renderManager) { mRenderManager = renderManager; } bool cull(osg::NodeVisitor*, osg::Drawable*, osg::State*) const override { if (!mRenderManager) return false; mRenderManager->collectDrawCalls(); return false; } 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(); } osg::Texture2D* texture = batch.mTexture; if(texture) state->applyTextureAttribute(0, texture); 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), (char*)vbo->getArray(0)->getDataPointer()); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), (char*)vbo->getArray(0)->getDataPointer() + 12); glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)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); // 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) { } // 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(); } 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; }; 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 { 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; destroyAllResources(); 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); } 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(); bool premultipliedAlpha = false; if (texture) { batch.mTexture = static_cast(texture)->getTexture(); if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() batch.mTexture->getUserValue("premultiplied alpha", premultipliedAlpha); } if (mInjectState) batch.mStateSet = mInjectState; else if (premultipliedAlpha) { // This is hacky, but MyGUI made it impossible to use a custom layer for a nested node, so state couldn't be injected 'properly' osg::ref_ptr stateSet = new osg::StateSet(); stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); batch.mStateSet = stateSet; } 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) { MapTexture::iterator item = mTextures.find(name); if (item != mTextures.end()) { delete item->second; mTextures.erase(item); } OSGTexture* texture = new OSGTexture(name, mImageManager); mTextures.insert(std::make_pair(name, texture)); return texture; } void RenderManager::destroyTexture(MyGUI::ITexture *texture) { if(texture == nullptr) return; MapTexture::iterator item = mTextures.find(texture->getName()); MYGUI_PLATFORM_ASSERT(item != mTextures.end(), "Texture '"<getName()<<"' not found"); mTextures.erase(item); delete texture; } MyGUI::ITexture* RenderManager::getTexture(const std::string &name) { if (name.empty()) return nullptr; MapTexture::const_iterator item = mTextures.find(name); if(item == mTextures.end()) { MyGUI::ITexture* tex = createTexture(name); tex->loadFromFile(name); return tex; } return item->second; } void RenderManager::destroyAllResources() { for (MapTexture::iterator it = mTextures.begin(); it != mTextures.end(); ++it) delete it->second; mTextures.clear(); } 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; } #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) 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"); } #endif } openmw-openmw-0.47.0/components/myguiplatform/myguirendermanager.hpp000066400000000000000000000074061413061077700261110ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H #include #include #include "myguicompat.h" namespace Resource { class ImageManager; } namespace osgViewer { class Viewer; } namespace osg { class Group; class Camera; class RenderInfo; class StateSet; } namespace osgMyGUI { class Drawable; 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; typedef std::map MapTexture; MapTexture mTextures; bool mIsInitialise; osg::ref_ptr mGuiRoot; float mInvScalingFactor; osg::StateSet* mInjectState; void destroyAllResources(); public: RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor); virtual ~RenderManager(); void initialise(); void shutdown(); 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mInfo; } bool checkTexture(MyGUI::ITexture* _texture); // setViewSize() is a part of MyGUI::RenderManager interface since 3.4.0 release #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3, 4, 0) void setViewSize(int width, int height); #else void setViewSize(int width, int height) override; #endif // registerShader() is a part of MyGUI::RenderManager interface since 3.4.1 release #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) override; #endif /*internal:*/ void collectDrawCalls(); }; } #endif openmw-openmw-0.47.0/components/myguiplatform/myguitexture.cpp000066400000000000000000000131061413061077700247640ustar00rootroot00000000000000#include "myguitexture.hpp" #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) : mImageManager(nullptr) , mTexture(texture) , 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; } #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void OSGTexture::setShader(const std::string& _shaderName) { Log(Debug::Warning) << "OSGTexture::setShader is not implemented"; } #endif } openmw-openmw-0.47.0/components/myguiplatform/myguitexture.hpp000066400000000000000000000044541413061077700247770ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H #include #include #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const #else #define OPENMW_MYGUI_CONST_GETTER_3_4_1 #endif namespace osg { class Image; class Texture2D; } namespace Resource { class ImageManager; } namespace osgMyGUI { class OSGTexture : public MyGUI::ITexture { std::string mName; Resource::ImageManager* mImageManager; osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; 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); virtual ~OSGTexture(); 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mLockedImage.valid(); } int getWidth() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mWidth; } int getHeight() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mHeight; } MyGUI::PixelFormat getFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mFormat; } MyGUI::TextureUsage getUsage() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mUsage; } size_t getNumElemBytes() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mNumElemBytes; } MyGUI::IRenderTarget *getRenderTarget() override; // setShader() is a part of MyGUI::RenderManager interface since 3.4.1 release #if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void setShader(const std::string& _shaderName) override; #endif /*internal:*/ osg::Texture2D *getTexture() const { return mTexture.get(); } }; } #endif openmw-openmw-0.47.0/components/myguiplatform/scalinglayer.cpp000066400000000000000000000111231413061077700246630ustar00rootroot00000000000000#include "scalinglayer.hpp" #include #include #include "myguicompat.h" 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() OPENMW_MYGUI_CONST_GETTER_3_4_1 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(); 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 viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float w = static_cast(viewSize.width); float h = static_cast(viewSize.height); float heightScale = (h / mViewSize.height); float widthScale = (w / mViewSize.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(); viewSize.width = static_cast(viewSize.width / scale); viewSize.height = static_cast(viewSize.height / scale); float hoffset = (globalViewSize.width - mViewSize.width*getScaleFactor())/2.f / static_cast(globalViewSize.width); float voffset = (globalViewSize.height - mViewSize.height*getScaleFactor())/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.47.0/components/myguiplatform/scalinglayer.hpp000066400000000000000000000021421413061077700246710ustar00rootroot00000000000000#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; private: void screenToLayerCoords(int& _left, int& _top) const; float getScaleFactor() const; }; } #endif openmw-openmw-0.47.0/components/nif/000077500000000000000000000000001413061077700173615ustar00rootroot00000000000000openmw-openmw-0.47.0/components/nif/base.hpp000066400000000000000000000033211413061077700210030ustar00rootroot00000000000000///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 "record.hpp" #include "niffile.hpp" #include "recordptr.hpp" #include "nifstream.hpp" #include "nifkey.hpp" namespace Nif { // 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 void read(NIFStream *nif) override { 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); nif->getUInt(); // Size of the record } } void post(NIFFile *nif) override { next.post(nif); } }; struct Controller : public Record { ControllerPtr next; int flags; float frequency, phase; float timeStart, timeStop; NamedPtr target; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; /// Has name, extra-data and controller struct Named : public Record { std::string name; ExtraPtr extra; ExtraList extralist; ControllerPtr controller; void read(NIFStream *nif) override { name = nif->getString(); if (nif->getVersion() < NIFStream::generateVersion(10,0,1,0)) extra.read(nif); else extralist.read(nif); controller.read(nif); } void post(NIFFile *nif) override { extra.post(nif); extralist.post(nif); controller.post(nif); } }; using NiSequenceStreamHelper = Named; } // Namespace #endif openmw-openmw-0.47.0/components/nif/controlled.cpp000066400000000000000000000067061413061077700222430ustar00rootroot00000000000000#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(); nif->getChar(); // always 1 if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103)) nif->getBoolean(); // Direct rendering if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4)) nif->getBoolean(); // NiPersistentSrcTextureRendererData is used instead of NiPixelData } 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,2,0)) { // Unused in NifSkope. Need to figure out what these do. /*bool spawnOnCollision = */nif->getBoolean(); /*bool dieOnCollision = */nif->getBoolean(); } } void NiPlanarCollider::read(NIFStream *nif) { NiParticleCollider::read(nif); /*unknown*/nif->getFloat(); for (int i=0;i<10;++i) /*unknown*/nif->getFloat(); mPlaneNormal = nif->getVector3(); mPlaneDistance = nif->getFloat(); } void NiParticleRotation::read(NIFStream *nif) { NiParticleModifier::read(nif); /* byte (0 or 1) float (1) float*3 */ nif->skip(17); } void NiSphericalCollider::read(NIFStream* nif) { NiParticleCollider::read(nif); mRadius = nif->getFloat(); mCenter = nif->getVector3(); } } openmw-openmw-0.47.0/components/nif/controlled.hpp000066400000000000000000000067621413061077700222520ustar00rootroot00000000000000/* 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 { void read(NIFStream *nif) override; osg::Vec3f mPlaneNormal; float mPlaneDistance; }; 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.47.0/components/nif/controller.cpp000066400000000000000000000207611413061077700222560ustar00rootroot00000000000000#include "controller.hpp" #include "node.hpp" #include "data.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 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.47.0/components/nif/controller.hpp000066400000000000000000000127541413061077700222660ustar00rootroot00000000000000/* 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 { 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; float size; float startTime; float stopTime; float emitRate; float lifetime; float lifetimeRandom; enum EmitFlags { 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; }; 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 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.47.0/components/nif/data.cpp000066400000000000000000000331501413061077700210000ustar00rootroot00000000000000#include "data.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 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, true, /*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) { //Chomp unused float if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) nif->getFloat(); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); mXRotations->read(nif, true); mYRotations->read(nif, true); mZRotations->read(nif, true); } 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.47.0/components/nif/data.hpp000066400000000000000000000130621413061077700210050ustar00rootroot00000000000000/* 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 "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 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; 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.47.0/components/nif/effect.cpp000066400000000000000000000030151413061077700213200ustar00rootroot00000000000000#include "effect.hpp" #include "node.hpp" namespace Nif { 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.47.0/components/nif/effect.hpp000066400000000000000000000047531413061077700213370ustar00rootroot00000000000000/* 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 { 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 } }; // 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; }; } // Namespace #endif openmw-openmw-0.47.0/components/nif/extra.cpp000066400000000000000000000030431413061077700212100ustar00rootroot00000000000000#include "extra.hpp" namespace Nif { 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(); } } openmw-openmw-0.47.0/components/nif/extra.hpp000066400000000000000000000045261413061077700212240ustar00rootroot00000000000000/* 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 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 { /* Two known meanings: "MRK" - marker, only visible in the editor, not rendered in-game "NCO" - 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; }; } // Namespace #endif openmw-openmw-0.47.0/components/nif/niffile.cpp000066400000000000000000000461741413061077700215150ustar00rootroot00000000000000#include "niffile.hpp" #include "effect.hpp" #include #include #include 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(stream); } NIFFile::~NIFFile() { for (Record* record : records) delete record; } template static Record* construct() { return new NodeType; } struct RecordFactoryEntry { using create_t = Record* (*)(); create_t mCreate; RecordType mType; }; ///These are all the record types we know how to read. static std::map makeFactory() { std::map factory; factory["NiNode"] = {&construct , RC_NiNode }; factory["NiSwitchNode"] = {&construct , RC_NiSwitchNode }; factory["NiLODNode"] = {&construct , RC_NiLODNode }; factory["AvoidNode"] = {&construct , RC_AvoidNode }; factory["NiCollisionSwitch"] = {&construct , RC_NiCollisionSwitch }; factory["NiBSParticleNode"] = {&construct , RC_NiBSParticleNode }; factory["NiBSAnimationNode"] = {&construct , RC_NiBSAnimationNode }; factory["NiBillboardNode"] = {&construct , RC_NiBillboardNode }; factory["NiTriShape"] = {&construct , RC_NiTriShape }; factory["NiTriStrips"] = {&construct , RC_NiTriStrips }; factory["NiLines"] = {&construct , RC_NiLines }; factory["NiParticles"] = {&construct , RC_NiParticles }; factory["NiRotatingParticles"] = {&construct , RC_NiParticles }; factory["NiAutoNormalParticles"] = {&construct , RC_NiParticles }; factory["NiCamera"] = {&construct , RC_NiCamera }; factory["RootCollisionNode"] = {&construct , RC_RootCollisionNode }; factory["NiTexturingProperty"] = {&construct , RC_NiTexturingProperty }; factory["NiFogProperty"] = {&construct , RC_NiFogProperty }; factory["NiMaterialProperty"] = {&construct , RC_NiMaterialProperty }; factory["NiZBufferProperty"] = {&construct , RC_NiZBufferProperty }; factory["NiAlphaProperty"] = {&construct , RC_NiAlphaProperty }; factory["NiVertexColorProperty"] = {&construct , RC_NiVertexColorProperty }; factory["NiShadeProperty"] = {&construct , RC_NiShadeProperty }; factory["NiDitherProperty"] = {&construct , RC_NiDitherProperty }; factory["NiWireframeProperty"] = {&construct , RC_NiWireframeProperty }; factory["NiSpecularProperty"] = {&construct , RC_NiSpecularProperty }; factory["NiStencilProperty"] = {&construct , RC_NiStencilProperty }; factory["NiVisController"] = {&construct , RC_NiVisController }; factory["NiGeomMorpherController"] = {&construct , RC_NiGeomMorpherController }; factory["NiKeyframeController"] = {&construct , RC_NiKeyframeController }; factory["NiAlphaController"] = {&construct , RC_NiAlphaController }; factory["NiRollController"] = {&construct , RC_NiRollController }; factory["NiUVController"] = {&construct , RC_NiUVController }; factory["NiPathController"] = {&construct , RC_NiPathController }; factory["NiMaterialColorController"] = {&construct , RC_NiMaterialColorController }; factory["NiBSPArrayController"] = {&construct , RC_NiBSPArrayController }; factory["NiParticleSystemController"] = {&construct , RC_NiParticleSystemController }; factory["NiFlipController"] = {&construct , RC_NiFlipController }; factory["NiAmbientLight"] = {&construct , RC_NiLight }; factory["NiDirectionalLight"] = {&construct , RC_NiLight }; factory["NiPointLight"] = {&construct , RC_NiLight }; factory["NiSpotLight"] = {&construct , RC_NiLight }; factory["NiTextureEffect"] = {&construct , RC_NiTextureEffect }; factory["NiVertWeightsExtraData"] = {&construct , RC_NiVertWeightsExtraData }; factory["NiTextKeyExtraData"] = {&construct , RC_NiTextKeyExtraData }; factory["NiStringExtraData"] = {&construct , RC_NiStringExtraData }; factory["NiGravity"] = {&construct , RC_NiGravity }; factory["NiPlanarCollider"] = {&construct , RC_NiPlanarCollider }; factory["NiSphericalCollider"] = {&construct , RC_NiSphericalCollider }; factory["NiParticleGrowFade"] = {&construct , RC_NiParticleGrowFade }; factory["NiParticleColorModifier"] = {&construct , RC_NiParticleColorModifier }; factory["NiParticleRotation"] = {&construct , RC_NiParticleRotation }; factory["NiFloatData"] = {&construct , RC_NiFloatData }; factory["NiTriShapeData"] = {&construct , RC_NiTriShapeData }; factory["NiTriStripsData"] = {&construct , RC_NiTriStripsData }; factory["NiLinesData"] = {&construct , RC_NiLinesData }; factory["NiVisData"] = {&construct , RC_NiVisData }; factory["NiColorData"] = {&construct , RC_NiColorData }; factory["NiPixelData"] = {&construct , RC_NiPixelData }; factory["NiMorphData"] = {&construct , RC_NiMorphData }; factory["NiKeyframeData"] = {&construct , RC_NiKeyframeData }; factory["NiSkinData"] = {&construct , RC_NiSkinData }; factory["NiUVData"] = {&construct , RC_NiUVData }; factory["NiPosData"] = {&construct , RC_NiPosData }; factory["NiParticlesData"] = {&construct , RC_NiParticlesData }; factory["NiRotatingParticlesData"] = {&construct , RC_NiParticlesData }; factory["NiAutoNormalParticlesData"] = {&construct , RC_NiParticlesData }; factory["NiSequenceStreamHelper"] = {&construct , RC_NiSequenceStreamHelper }; factory["NiSourceTexture"] = {&construct , RC_NiSourceTexture }; factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; factory["NiLookAtController"] = {&construct , RC_NiLookAtController }; factory["NiPalette"] = {&construct , RC_NiPalette }; factory["NiIntegerExtraData"] = {&construct , RC_NiIntegerExtraData }; factory["NiIntegersExtraData"] = {&construct , RC_NiIntegersExtraData }; factory["NiBinaryExtraData"] = {&construct , RC_NiBinaryExtraData }; factory["NiBooleanExtraData"] = {&construct , RC_NiBooleanExtraData }; factory["NiVectorExtraData"] = {&construct , RC_NiVectorExtraData }; factory["NiColorExtraData"] = {&construct , RC_NiColorExtraData }; factory["NiFloatExtraData"] = {&construct , RC_NiFloatExtraData }; factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; factory["NiStringPalette"] = {&construct , RC_NiStringPalette }; factory["NiBoolData"] = {&construct , RC_NiBoolData }; factory["NiSkinPartition"] = {&construct , RC_NiSkinPartition }; factory["BSXFlags"] = {&construct , RC_BSXFlags }; factory["BSBound"] = {&construct , RC_BSBound }; factory["NiTransformData"] = {&construct , RC_NiKeyframeData }; factory["BSFadeNode"] = {&construct , RC_NiNode }; factory["bhkBlendController"] = {&construct , RC_bhkBlendController }; factory["NiFloatInterpolator"] = {&construct , RC_NiFloatInterpolator }; factory["NiBoolInterpolator"] = {&construct , RC_NiBoolInterpolator }; factory["NiPoint3Interpolator"] = {&construct , RC_NiPoint3Interpolator }; factory["NiTransformController"] = {&construct , RC_NiKeyframeController }; factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; return factory; } ///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) { NIFStream nif (this, stream); // Check the header string std::string head = nif.getVersionString(); static const std::array verStrings = { "NetImmerse File Format", "Gamebryo File Format" }; bool supported = false; for (const std::string& verString : verStrings) { supported = (head.compare(0, verString.size(), verString) == 0); if (supported) break; } if (!supported) fail("Invalid NIF header: " + head); supported = false; // 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 }; for (uint32_t supportedVer : supportedVers) { supported = (ver == supportedVer); if (supported) break; } if (!supported) { 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++) { Record *r = nullptr; 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()); } } std::map::const_iterator entry = factories.find(rec); if (entry != factories.end()) { r = entry->second.mCreate (); r->recType = entry->second.mType; } else fail("Unknown record type " + rec); if (!supported) 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; records[i] = r; r->read(&nif); } 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]; } 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 (Record* record : records) record->post(this); } void NIFFile::setUseSkinning(bool skinning) { mUseSkinning = skinning; } bool NIFFile::getUseSkinning() const { return mUseSkinning; } bool NIFFile::sLoadUnsupportedFiles = false; void NIFFile::setLoadUnsupportedFiles(bool load) { sLoadUnsupportedFiles = load; } } openmw-openmw-0.47.0/components/nif/niffile.hpp000066400000000000000000000110221413061077700215020ustar00rootroot00000000000000///Main header for reading .nif files #ifndef OPENMW_COMPONENTS_NIF_NIFFILE_HPP #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP #include #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 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; /// 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 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 void fail(const std::string &msg) const { std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so void warn(const std::string &msg) const { Log(Debug::Warning) << " NIFFile Warning: " << msg << "\nFile: " << filename; } /// Open a NIF stream. The name is used for error messages. NIFFile(Files::IStreamPtr stream, const std::string &name); ~NIFFile(); /// Get a given record Record *getRecord(size_t index) const override { Record *res = records.at(index); return res; } /// 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 { if (index == std::numeric_limits::max()) return std::string(); return strings.at(index); } /// 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; } /// 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.47.0/components/nif/nifkey.hpp000066400000000000000000000130231413061077700213560ustar00rootroot00000000000000///File to handle keys used by nif file records #ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP #define OPENMW_COMPONENTS_NIF_NIFKEY_HPP #include "nifstream.hpp" #include #include #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_Linear; MapType mKeys; //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) void read(NIFStream *nif, bool force = false, bool morph = false) { assert(nif); mInterpolationType = InterpolationType_Unknown; if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106)) nif->getString(); // Frame name size_t count = nif->getUInt(); if (count == 0 && !force && !morph) return; 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 return; } mKeys.clear(); mInterpolationType = nif->getUInt(); KeyType key = {}; NIFStream &nifReference = *nif; if (mInterpolationType == InterpolationType_Linear || mInterpolationType == InterpolationType_Constant) { for(size_t i = 0;i < count;i++) { float time = nif->getFloat(); readValue(nifReference, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_Quadratic) { for(size_t i = 0;i < count;i++) { float time = nif->getFloat(); readQuadratic(nifReference, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_TBC) { for(size_t i = 0;i < count;i++) { float time = nif->getFloat(); readTBC(nifReference, key); mKeys[time] = key; } } //XYZ keys aren't actually read here. //data.hpp 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(mInterpolationType == InterpolationType_XYZ) { //Don't try to read XYZ keys into the wrong part if ( count != 1 ) { std::stringstream error; error << "XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: " << count; nif->file->fail(error.str()); } } else if (mInterpolationType == InterpolationType_Unknown) { if (count != 0) nif->file->fail("Interpolation type 0 doesn't work with keys"); } else { std::stringstream error; error << "Unhandled interpolation type: " << mInterpolationType; nif->file->fail(error.str()); } } 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.47.0/components/nif/nifstream.cpp000066400000000000000000000025721413061077700220630ustar00rootroot00000000000000#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.47.0/components/nif/nifstream.hpp000066400000000000000000000162351413061077700220710ustar00rootroot00000000000000///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 (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"); 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.47.0/components/nif/niftypes.hpp000066400000000000000000000042711413061077700217370ustar00rootroot00000000000000/* 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.47.0/components/nif/node.cpp000066400000000000000000000000001413061077700210000ustar00rootroot00000000000000openmw-openmw-0.47.0/components/nif/node.hpp000066400000000000000000000260061413061077700210230ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIF_NODE_HPP #define OPENMW_COMPONENTS_NIF_NODE_HPP #include "controlled.hpp" #include "extra.hpp" #include "data.hpp" #include "property.hpp" #include "niftypes.hpp" #include "controller.hpp" #include "base.hpp" #include namespace Nif { struct NiNode; struct NiBoundingVolume { enum Type { 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 axis; 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::Vec3f center, normal; }; unsigned int type; NiSphereBV sphere; NiBoxBV box; NiCapsuleBV capsule; NiLozengeBV lozenge; std::vector children; NiHalfSpaceBV plane; void read(NIFStream* nif) { type = nif->getUInt(); switch (type) { case SPHERE_BV: { sphere.center = nif->getVector3(); sphere.radius = nif->getFloat(); break; } case BOX_BV: { box.center = nif->getVector3(); box.axis = 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(); 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: { plane.center = nif->getVector3(); plane.normal = nif->getVector3(); break; } default: { std::stringstream error; error << "Unhandled NiBoundingVolume type: " << type; nif->file->fail(error.str()); } } } }; /** 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 { // 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; void read(NIFStream *nif) override { 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)) nif->skip(4); parent = nullptr; isBone = false; } void post(NIFFile *nif) override { Named::post(nif); props.post(nif); } // Parent node, or nullptr for the root node. As far as I'm aware, only // NiNodes (or types derived from NiNodes) can be parents. NiNode *parent; bool isBone{false}; void setBone() { isBone = true; } }; struct NiNode : Node { NodeList children; NodeList effects; enum Flags { Flag_Hidden = 0x0001, Flag_MeshCollision = 0x0002, Flag_BBoxCollision = 0x0004, Flag_ActiveCollision = 0x0020 }; enum BSAnimFlags { AnimFlag_AutoPlay = 0x0020 }; enum BSParticleFlags { ParticleFlag_AutoPlay = 0x0020, ParticleFlag_LocalSpace = 0x0080 }; enum ControllerFlags { ControllerFlag_Active = 0x8 }; void read(NIFStream *nif) override { 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. if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) { static_cast(this)->trafo = Nif::Transformation::getIdentity(); } } void post(NIFFile *nif) override { 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]->parent = this; } } }; 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) { 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(); } }; NiGeometryDataPtr data; NiSkinInstancePtr skin; MaterialData material; BSShaderPropertyPtr shaderprop; NiAlphaPropertyPtr alphaprop; void read(NIFStream *nif) override { 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 post(NIFFile *nif) override { Node::post(nif); data.post(nif); skin.post(nif); shaderprop.post(nif); alphaprop.post(nif); if (recType != RC_NiParticles && !skin.empty()) nif->setUseSkinning(true); } }; struct NiTriShape : NiGeometry {}; struct BSLODTriShape : NiTriShape { unsigned int lod0, lod1, lod2; void read(NIFStream *nif) override { NiTriShape::read(nif); lod0 = nif->getUInt(); lod1 = nif->getUInt(); lod2 = nif->getUInt(); } }; 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) { 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(); } }; Camera cam; void read(NIFStream *nif) override { Node::read(nif); cam.read(nif); nif->getInt(); // -1 nif->getInt(); // 0 if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0)) nif->getInt(); // 0 } }; // 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 { NiNode::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) switchFlags = nif->getUShort(); initialIndex = nif->getUInt(); } }; struct NiLODNode : public NiSwitchNode { osg::Vec3f lodCenter; struct LODRange { float minRange; float maxRange; }; std::vector lodLevels; void read(NIFStream *nif) override { 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); } } }; } // Namespace #endif openmw-openmw-0.47.0/components/nif/property.cpp000066400000000000000000000132221413061077700217510ustar00rootroot00000000000000#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 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.47.0/components/nif/property.hpp000066400000000000000000000212261413061077700217610ustar00rootroot00000000000000/* 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); }; /* 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(); } }; struct BSShaderProperty : public NiShadeProperty { enum BSShaderType { SHADER_TALL_GRASS = 0, SHADER_DEFAULT = 1, SHADER_SKY = 10, SHADER_SKIN = 14, SHADER_WATER = 17, SHADER_LIGHTING30 = 29, SHADER_TILE = 32, SHADER_NOLIGHTING = 33 }; 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; }; 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; }; 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(); } }; struct NiSpecularProperty : public Property { unsigned short flags; void read(NIFStream* nif) override { Property::read(nif); flags = nif->getUShort(); } }; struct NiWireframeProperty : public Property { unsigned short flags; void read(NIFStream* nif) override { Property::read(nif); flags = nif->getUShort(); } }; // 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 { /* In NiAlphaProperty, the flags have the following meaning: Bit 0 : alpha blending enable Bits 1-4 : source blend mode Bits 5-8 : destination blend mode Bit 9 : alpha test enable Bit 10-12 : alpha test mode Bit 13 : no sorter flag ( disables triangle sorting ) 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 { }; 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.47.0/components/nif/record.hpp000066400000000000000000000066201413061077700213540ustar00rootroot00000000000000/* 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_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_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 }; /// Base class for all records struct Record { // Record type and type name int 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.47.0/components/nif/recordptr.hpp000066400000000000000000000111401413061077700220730ustar00rootroot00000000000000#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; 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 NodeList = RecordListT; using PropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiFloatInterpolatorList = RecordListT; using NiTriStripsDataList = RecordListT; } // Namespace #endif openmw-openmw-0.47.0/components/nifbullet/000077500000000000000000000000001413061077700205715ustar00rootroot00000000000000openmw-openmw-0.47.0/components/nifbullet/bulletnifloader.cpp000066400000000000000000000343701413061077700244570ustar00rootroot00000000000000#include "bulletnifloader.hpp" #include #include #include #include #include #include #include #include #include #include namespace { osg::Matrixf getWorldTransform(const Nif::Node *node) { if(node->parent != nullptr) return node->trafo.toMatrix() * getWorldTransform(node->parent); return node->trafo.toMatrix(); } 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 = strip[0], b = strip[0], 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) ); } } } } } void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) { if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape) fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); else if (geometry->recType == Nif::RC_NiTriStrips) fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); } } namespace NifBullet { osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; mCompoundShape.reset(); mStaticMesh.reset(); mAvoidStaticMesh.reset(); 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(); 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.extents); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center); std::unique_ptr compound (new btCompoundShape); std::unique_ptr boxShape(new btBoxShape(extents)); btTransform transform = btTransform::getIdentity(); transform.setOrigin(center); compound->addChildShape(transform, boxShape.get()); boxShape.release(); mShape->mCollisionShape = 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 autogenerated = hasAutoGeneratedCollision(node); handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); } if (mCompoundShape) { if (mStaticMesh) { btTransform trans; trans.setIdentity(); std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); mCompoundShape->addChildShape(trans, child.get()); child.release(); mStaticMesh.release(); } mShape->mCollisionShape = mCompoundShape.release(); } else if (mStaticMesh) { mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); mStaticMesh.release(); } if (mAvoidStaticMesh) { mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); 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.extents = node->bounds.box.extents; mShape->mCollisionBox.center = 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->flags & Nif::NiNode::Flag_BBoxCollision) { return true; } } const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) { if (findBoundingBox(list[i].getPtr(), filename)) return true; } } } return false; } bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) { const Nif::NiNode *ninode = dynamic_cast(rootNode); if(ninode) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) { if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode) return false; } } } return true; } void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { // TODO: allow on-the fly collision switching via toggling this flag if (node->recType == Nif::RC_NiCollisionSwitch && !(node->flags & Nif::NiNode::Flag_ActiveCollision)) 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->flags & Nif::NiNode::ControllerFlag_Active)) 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) { // No collision. Use an internal flag setting to mark this. flags |= 0x800; } 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(node, flags, getWorldTransform(node), isAnimated, avoid); } } // For NiNodes, loop through children const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf &transform, bool isAnimated, bool avoid) { assert(nifNode != nullptr); // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) return; auto niGeometry = static_cast(nifNode); if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) return; if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape) { if (niGeometry->data->recType != Nif::RC_NiTriShapeData) return; auto data = static_cast(niGeometry->data.getPtr()); if (data->triangles.empty()) return; } else if (niGeometry->recType == Nif::RC_NiTriStrips) { if (niGeometry->data->recType != Nif::RC_NiTriStripsData) return; auto data = static_cast(niGeometry->data.getPtr()); if (data->strips.empty()) return; } if (!niGeometry->skin.empty()) isAnimated = false; if (isAnimated) { if (!mCompoundShape) mCompoundShape.reset(new btCompoundShape); std::unique_ptr childMesh(new btTriangleMesh); fillTriangleMesh(*childMesh, niGeometry); std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); float scale = nifNode->trafo.scale; const Nif::Node* parent = nifNode; while (parent->parent) { parent = parent->parent; scale *= parent->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(nifNode->recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); } else if (avoid) { if (!mAvoidStaticMesh) mAvoidStaticMesh.reset(new btTriangleMesh(false)); fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform); } else { if (!mStaticMesh) mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position fillTriangleMesh(*mStaticMesh, niGeometry, transform); } } } // namespace NifBullet openmw-openmw-0.47.0/components/nifbullet/bulletnifloader.hpp000066400000000000000000000033161413061077700244600ustar00rootroot00000000000000#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; } namespace NifBullet { /** *Load bulletShape from NIF files. */ class BulletNifLoader { public: void warn(const std::string &msg) { Log(Debug::Warning) << "NIFLoader: Warn: " << msg; } 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, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); bool hasAutoGeneratedCollision(const Nif::Node *rootNode); void handleNiTriShape(const Nif::Node *nifNode, int flags, 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.47.0/components/nifosg/000077500000000000000000000000001413061077700200725ustar00rootroot00000000000000openmw-openmw-0.47.0/components/nifosg/controller.cpp000066400000000000000000000443271413061077700227730ustar00rootroot00000000000000#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(static_cast((ctrl->flags&0x6) >> 1)) { } float ControllerFunction::calculate(float value) const { float time = mFrequency * value + mPhase; if (time >= mStartTime && time <= mStopTime) return time; switch (mExtrapolationMode) { case 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 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 Constant: default: return std::min(mStopTime, std::max(mStartTime, time)); } } float ControllerFunction::getMaximum() const { return mStopTime; } KeyframeController::KeyframeController() { } KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) , mZRotations(copy.mZRotations) , mTranslations(copy.mTranslations) , mScales(copy.mScales) { } KeyframeController::KeyframeController(const Nif::NiKeyframeData *data) : mRotations(data->mRotations) , mXRotations(data->mXRotations, 0.f) , mYRotations(data->mYRotations, 0.f) , mZRotations(data->mZRotations, 0.f) , mTranslations(data->mTranslations, osg::Vec3f()) , mScales(data->mScales, 1.f) { } KeyframeController::KeyframeController(const Nif::NiTransformInterpolator* interpolator) : mRotations(interpolator->data->mRotations, interpolator->defaultRot) , mXRotations(interpolator->data->mXRotations, 0.f) , mYRotations(interpolator->data->mYRotations, 0.f) , mZRotations(interpolator->data->mZRotations, 0.f) , mTranslations(interpolator->data->mTranslations, interpolator->defaultPos) , mScales(interpolator->data->mScales, interpolator->defaultScale) { } KeyframeController::KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot) : mRotations(Nif::QuaternionKeyMapPtr(), rot) , mXRotations(Nif::FloatKeyMapPtr(), 0.f) , mYRotations(Nif::FloatKeyMapPtr(), 0.f) , mZRotations(Nif::FloatKeyMapPtr(), 0.f) , mTranslations(Nif::Vector3KeyMapPtr(), pos) , mScales(Nif::FloatKeyMapPtr(), scale) { } 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::Vec3f(1,0,0)); osg::Quat yr(yrot, osg::Vec3f(0,1,0)); osg::Quat zr(zrot, osg::Vec3f(0,0,1)); return (xr*yr*zr); } osg::Vec3f KeyframeController::getTranslation(float time) const { if(!mTranslations.empty()) return mTranslations.interpKey(time); return osg::Vec3f(); } void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) { if (hasInput()) { NifOsg::MatrixTransform* trans = static_cast(node); osg::Matrix mat = trans->getMatrix(); float time = getInputValue(nv); Nif::Matrix3& rot = trans->mRotationScale; bool setRot = false; if(!mRotations.empty()) { mat.setRotate(mRotations.interpKey(time)); setRot = true; } else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) { mat.setRotate(getXYZRotation(time)); setRot = true; } else { // no rotation specified, use the previous value for (int i=0;i<3;++i) for (int j=0;j<3;++j) mat(j,i) = rot.mValues[i][j]; // NB column/row major difference } if (setRot) // copy the new values back for (int i=0;i<3;++i) for (int j=0;j<3;++j) rot.mValues[i][j] = mat(j,i); // NB column/row major difference float& scale = trans->mScale; if(!mScales.empty()) scale = mScales.interpKey(time); for (int i=0;i<3;++i) for (int j=0;j<3;++j) mat(i,j) *= scale; if(!mTranslations.empty()) mat.setTrans(mTranslations.interpKey(time)); trans->setMatrix(mat); } traverse(node, nv); } GeomMorpherController::GeomMorpherController() { } GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, const osg::CopyOp ©op) : osg::Drawable::UpdateCallback(copy, copyop) , Controller(copy) , 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::update(osg::NodeVisitor *nv, osg::Drawable *drawable) { SceneUtil::MorphGeometry* morphGeom = static_cast(drawable); if (hasInput()) { if (mKeyFrames.size() <= 1) return; float input = getInputValue(nv); int i = 0; 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 = morphGeom->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); morphGeom->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) : osg::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) : osg::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mStartingTime(copy.mStartingTime) { } void RollController::operator() (osg::Node* 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)); osg::MatrixTransform* transform = static_cast(node); osg::Matrix matrix = transform->getMatrix(); // 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. matrix = osg::Matrix::rotate(value * duration * 60.f, 0, 0, 1) * matrix; transform->setMatrix(matrix); } } 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) : osg::NodeCallback(copy, copyop) , Controller(copy) , mEmitStart(copy.mEmitStart) , mEmitStop(copy.mEmitStop) { } void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv) { osgParticle::ParticleProcessor* emitter = static_cast(node); if (hasInput()) { float time = getInputValue(nv); emitter->getParticleSystem()->setFrozen(false); emitter->setEnabled(time >= mEmitStart && time < mEmitStop); } else emitter->getParticleSystem()->setFrozen(true); traverse(node, nv); } PathController::PathController(const PathController ©, const osg::CopyOp ©op) : osg::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() (osg::Node* node, osg::NodeVisitor* nv) { if (mPath.empty() || mPercent.empty() || !hasInput()) { traverse(node, nv); return; } osg::MatrixTransform* trans = static_cast(node); osg::Matrix mat = trans->getMatrix(); float time = getInputValue(nv); float percent = getPercent(time); osg::Vec3f pos(mPath.interpKey(percent)); mat.setTrans(pos); trans->setMatrix(mat); traverse(node, nv); } } openmw-openmw-0.47.0/components/nifosg/controller.hpp000066400000000000000000000331271413061077700227740ustar00rootroot00000000000000#ifndef COMPONENTS_NIFOSG_CONTROLLER_H #define COMPONENTS_NIFOSG_CONTROLLER_H #include #include #include #include #include #include #include #include #include #include #include #include namespace osg { class Material; } namespace NifOsg { // 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; enum ExtrapolationMode { Cycle = 0, Reverse = 1, Constant = 2 }; ExtrapolationMode mExtrapolationMode; public: ControllerFunction(const Nif::Controller *ctrl); float calculate(float value) const override; float getMaximum() const override; }; /// Must be set on a SceneUtil::MorphGeometry. class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller { public: GeomMorpherController(const Nif::NiGeomMorpherController* ctrl); GeomMorpherController(); GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, GeomMorpherController) void update(osg::NodeVisitor* nv, osg::Drawable* drawable) override; private: std::vector mKeyFrames; }; class KeyframeController : public SceneUtil::KeyframeController { public: // This is used if there's no interpolator but there is data (Morrowind meshes). KeyframeController(const Nif::NiKeyframeData *data); // This is used if the interpolator has data. KeyframeController(const Nif::NiTransformInterpolator* interpolator); // This is used if there are default values available (e.g. from a data-less interpolator). // If there's neither keyframe data nor an interpolator a KeyframeController must not be created. KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot); KeyframeController(); KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, KeyframeController) osg::Vec3f getTranslation(float time) const override; void operator() (osg::Node*, osg::NodeVisitor*) override; private: QuaternionInterpolator mRotations; FloatInterpolator mXRotations; FloatInterpolator mYRotations; FloatInterpolator mZRotations; Vec3Interpolator mTranslations; FloatInterpolator mScales; osg::Quat getXYZRotation(float time) const; }; 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 osg::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) override; }; class RollController : public osg::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::Node* node, osg::NodeVisitor* nv) override; 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 osg::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() (osg::Node* node, osg::NodeVisitor* nv) override; private: float mEmitStart; float mEmitStop; }; class PathController : public osg::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() (osg::Node*, osg::NodeVisitor*) override; private: Vec3Interpolator mPath; FloatInterpolator mPercent; int mFlags{0}; float getPercent(float time) const; }; } #endif openmw-openmw-0.47.0/components/nifosg/matrixtransform.cpp000066400000000000000000000010701413061077700240340ustar00rootroot00000000000000#include "matrixtransform.hpp" namespace NifOsg { MatrixTransform::MatrixTransform() : osg::MatrixTransform() { } 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) { } } openmw-openmw-0.47.0/components/nifosg/matrixtransform.hpp000066400000000000000000000017431413061077700240500ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H #define OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H #include #include namespace NifOsg { class MatrixTransform : public osg::MatrixTransform { public: MatrixTransform(); 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; }; } #endif openmw-openmw-0.47.0/components/nifosg/nifloader.cpp000066400000000000000000003065241413061077700225530ustar00rootroot00000000000000#include "nifloader.hpp" #include #include #include #include #include #include #include #include // resource #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 "matrixtransform.hpp" #include "particle.hpp" namespace { void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) { const Nif::NiNode* ninode = dynamic_cast(node); if (ninode) { 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, std::vector& out) { if (nifNode->parent) collectDrawableProperties(nifNode->parent, 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. // Must be set as a cull callback. class BillboardCallback : public osg::NodeCallback { public: BillboardCallback() { } BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) { } META_Object(NifOsg, BillboardCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); 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, nv); cv->popModelViewMatrix(); } }; void extractTextKeys(const Nif::NiTextKeyExtraData *tk, SceneUtil::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) { const std::string &str = tk->list[i].text; std::string::size_type pos = 0; while(pos < str.length()) { if(::isspace(str[pos])) { pos++; continue; } std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); if(nextpos != std::string::npos) { do { nextpos--; } while(nextpos > pos && ::isspace(str[nextpos])); nextpos++; } else if(::isspace(*str.rbegin())) { std::string::const_iterator last = str.end(); do { --last; } while(last != str.begin() && ::isspace(*last)); nextpos = std::distance(str.begin(), ++last); } std::string result = str.substr(pos, nextpos-pos); Misc::StringUtils::lowerCaseInPlace(result); textkeys.emplace(tk->list[i].time, std::move(result)); pos = nextpos; } } } } 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; // 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(handleKeyframeController(key)); callback->setFunction(std::shared_ptr(new NifOsg::ControllerFunction(key))); 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, 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); 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; for (size_t i = 0; i parent == nullptr && !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); } } 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); } void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) { bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; if (autoPlay) toSetup->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); toSetup->setFunction(std::shared_ptr(new ControllerFunction(ctrl))); } 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; } 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; } 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"); bool wrapT = textureEffect->clamp & 0x1; bool wrapS = (textureEffect->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); 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->parent && 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); if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) { node->setNodeMask(Loader::getIntersectionDisabledNodeMask()); // This node must not be combined with another node. dataVariance = osg::Object::DYNAMIC; } node->setDataVariance(dataVariance); return node; } osg::ref_ptr handleNode(const Nif::Node* nifNode, 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()); // 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"); } } } if (nifNode->recType == Nif::RC_NiBSAnimationNode || nifNode->recType == Nif::RC_NiBSParticleNode) animflags = nifNode->flags; // 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->flags & Nif::NiNode::Flag_Hidden) { 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()); } 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, node, composite, boundTextures, animflags); else handleSkinnedGeometry(nifNode, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); } } if (nifNode->recType == Nif::RC_NiParticles) handleParticleSystem(nifNode, 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; } 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; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); } } 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->flags & Nif::NiNode::ControllerFlag_Active)) 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); } } } static osg::ref_ptr handleKeyframeController(const Nif::NiKeyframeController* keyctrl) { osg::ref_ptr ctrl; if (!keyctrl->interpolator.empty()) { const Nif::NiTransformInterpolator* interp = keyctrl->interpolator.getPtr(); if (!interp->data.empty()) ctrl = new NifOsg::KeyframeController(interp); else ctrl = new NifOsg::KeyframeController(interp->defaultScale, interp->defaultPos, interp->defaultRot); } else if (!keyctrl->data.empty()) { ctrl = new NifOsg::KeyframeController(keyctrl->data.getPtr()); } return ctrl; } 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->flags & Nif::NiNode::ControllerFlag_Active)) 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(handleKeyframeController(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->flags & Nif::NiNode::ControllerFlag_Active)) 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; osg::ref_ptr osgctrl; auto targetColor = static_cast(matctrl->targetColor); 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->flags & Nif::NiNode::ControllerFlag_Active)) 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); osg::Vec4f partcolor (1.f,1.f,1.f,1.f); if (particle.vertex < particledata->colors.size()) partcolor = particledata->colors[particle.vertex]; 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) { getAllNiNodes(partctrl->emitter.getPtr(), targets); } osg::ref_ptr emitter = new Emitter(targets); osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter; if (partctrl->emitFlags & Nif::NiParticleSystemController::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); 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); } mEmitterQueue.clear(); } void handleParticleSystem(const Nif::Node *nifNode, 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->flags & Nif::NiNode::ControllerFlag_Active)) 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(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f))); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); partsys->setFreezeOnCull(true); 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, 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); } // create partsys stateset in order to pass in ShaderVisitor like all other Drawables partsys->getOrCreateStateSet(); } 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 (const unsigned int uvSet : boundTextures) { if (uvSet >= uvlist.size()) { Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename; if (!uvlist.empty()) geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[0].size(), uvlist[0].data()), osg::Array::BIND_PER_VERTEX); continue; } geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX); textureStage++; } } void handleNiGeometry(const Nif::Node *nifNode, 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(), (unsigned short*)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(), (unsigned short*)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, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } void handleGeometry(const Nif::Node* nifNode, 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, 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->flags & Nif::NiNode::ControllerFlag_Active)) 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; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; 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, 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, 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::NEVER; // NifSkope says this is GL_ALWAYS, but in MW it's GL_NEVER 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); texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); 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: break; case Nif::NiTexturingProperty::GlossTexture: { // Not used by the vanilla engine. MCP (Morrowind Code Patch) adds an option to use Gloss maps: // "- Gloss map fix. Morrowind removed gloss map entries from model files after loading them. This stops Morrowind from removing them." // Log(Debug::Info) << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used."; continue; } 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; bool wrapT = tex.clamp & 0x1; bool wrapS = (tex.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); 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; texEnv->setMode(osg::TexEnv::MODULATE); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::DetailTexture) { osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; texEnv->setScale_RGB(2.f); texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); texEnv->setOperand1_Alpha(osg::TexEnvCombine::SRC_ALPHA); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_Alpha(osg::TexEnvCombine::TEXTURE); texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::BumpTexture) { // 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::DecalTexture) { osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); texEnv->setSource0_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); texEnv->setSource1_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_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; 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); } } const std::string& getNVShaderPrefix(unsigned int type) const { static const std::map mapping = { {Nif::BSShaderProperty::SHADER_TALL_GRASS, std::string()}, {Nif::BSShaderProperty::SHADER_DEFAULT, "nv_default"}, {Nif::BSShaderProperty::SHADER_SKY, std::string()}, {Nif::BSShaderProperty::SHADER_SKIN, std::string()}, {Nif::BSShaderProperty::SHADER_WATER, std::string()}, {Nif::BSShaderProperty::SHADER_LIGHTING30, std::string()}, {Nif::BSShaderProperty::SHADER_TILE, std::string()}, {Nif::BSShaderProperty::SHADER_NOLIGHTING, "nv_nolighting"}, }; auto prefix = mapping.find(type); if (prefix == mapping.end()) Log(Debug::Warning) << "Unknown shader type " << type << " in " << mFilename; else if (prefix->second.empty()) Log(Debug::Warning) << "Unhandled shader type " << type << " in " << mFilename; else return prefix->second; return mapping.at(Nif::BSShaderProperty::SHADER_DEFAULT); } void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { 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) { 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->flags == 0 ? osg::PolygonMode::FILL : osg::PolygonMode::LINE); 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(); // Depth test flag stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON : osg::StateAttribute::OFF); osg::ref_ptr depth = new osg::Depth; // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); // Morrowind ignores depth test function 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", getNVShaderPrefix(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", getNVShaderPrefix(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()); bool wrapT = texprop->clamp & 0x1; bool wrapS = (texprop->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); 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; } // 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) { osg::StateSet* stateset = node->getOrCreateStateSet(); // 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; int lightmode = 1; float emissiveMult = 1.f; for (const Nif::Property* property : properties) { switch (property->recType) { case Nif::RC_NiSpecularProperty: { // Specular property can turn specular lighting off. auto specprop = static_cast(property); specEnabled = specprop->flags & 1; 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->flags&1) { osg::ref_ptr blendFunc (new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), getBlendMode((alphaprop->flags>>5)&0xf))); // 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); stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else stateset->setRenderBinToInherit(); } else { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); stateset->setRenderBinToInherit(); } if((alphaprop->flags>>9)&1) { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } else { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); } 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 (!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); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; 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.47.0/components/nifosg/nifloader.hpp000066400000000000000000000043701413061077700225520ustar00rootroot00000000000000#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.47.0/components/nifosg/particle.cpp000066400000000000000000000365141413061077700224120ustar00rootroot00000000000000#include "particle.hpp" #include #include #include #include #include #include #include #include #include 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); } #else state.Normal(0.3, 0.3, 0.3); #endif osgParticle::ParticleSystem::drawImplementation(renderInfo); } void InverseWorldMatrix::operator()(osg::Node *node, osg::NodeVisitor *nv) { if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { osg::NodePath path = nv->getNodePath(); path.pop_back(); osg::MatrixTransform* trans = static_cast(node); osg::Matrix mat = osg::computeLocalToWorld( path ); mat.orthoNormalize(mat); // don't undo the scale mat.invert(mat); trans->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() { } 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))) { } Emitter::Emitter(const std::vector &targets) : mTargets(targets) { } void Emitter::setShooter(osgParticle::Shooter *shooter) { mShooter = shooter; } void Emitter::setPlacer(osgParticle::Placer *placer) { mPlacer = placer; } void Emitter::setCounter(osgParticle::Counter *counter) { mCounter = counter; } 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; if (!mTargets.empty()) { int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); int randomRecIndex = mTargets[randomIndex]; // we could use a map here for faster lookup FindGroupByRecIndex visitor(randomRecIndex); getParent(0)->accept(visitor); if (!visitor.mFound) { Log(Debug::Info) << "Can't find emitter node" << randomRecIndex; return; } osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; } emitterToPs.orthoNormalize(emitterToPs); for (int i=0; icreateParticle(nullptr); if (P) { 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) , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance) { } PlanarCollider::PlanarCollider() : mBounceFactor(0.f) { } PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp ©op) : osgParticle::Operator(copy, copyop) , mBounceFactor(copy.mBounceFactor) , mPlane(copy.mPlane) , mPlaneInParticleSpace(copy.mPlaneInParticleSpace) { } void PlanarCollider::beginOperate(osgParticle::Program *program) { mPlaneInParticleSpace = mPlane; if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF) mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix()); } void PlanarCollider::operate(osgParticle::Particle *particle, double dt) { float dotproduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal(); if (dotproduct > 0) { osg::BoundingSphere bs(particle->getPosition(), 0.f); if (mPlaneInParticleSpace.intersect(bs) == 1) { osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * dotproduct); 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.47.0/components/nifosg/particle.hpp000066400000000000000000000171611413061077700224140ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #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 osg::NodeCallback { public: InverseWorldMatrix() { } InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op) : osg::Object(), osg::NodeCallback() { } META_Object(NifOsg, InverseWorldMatrix) void operator()(osg::Node* node, osg::NodeVisitor* nv) override; }; 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(); 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; osg::Plane mPlane; osg::Plane 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); void setPlacer(osgParticle::Placer* placer); void setCounter(osgParticle::Counter* counter); private: // NIF Record indices std::vector mTargets; osg::ref_ptr mPlacer; osg::ref_ptr mShooter; osg::ref_ptr mCounter; }; } #endif openmw-openmw-0.47.0/components/process/000077500000000000000000000000001413061077700202635ustar00rootroot00000000000000openmw-openmw-0.47.0/components/process/processinvoker.cpp000066400000000000000000000144311413061077700240460ustar00rootroot00000000000000#include "processinvoker.hpp" #include #include #include #include #include #include Process::ProcessInvoker::ProcessInvoker() { 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; 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) { 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) { 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(); } } openmw-openmw-0.47.0/components/process/processinvoker.hpp000066400000000000000000000021661413061077700240550ustar00rootroot00000000000000#ifndef PROCESSINVOKER_HPP #define PROCESSINVOKER_HPP #include #include #include namespace Process { class ProcessInvoker : public QObject { Q_OBJECT public: ProcessInvoker(); ~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); private: QProcess *mProcess; QString mName; QStringList mArguments; private slots: void processError(QProcess::ProcessError error); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); }; } #endif // PROCESSINVOKER_HPP openmw-openmw-0.47.0/components/resource/000077500000000000000000000000001413061077700204345ustar00rootroot00000000000000openmw-openmw-0.47.0/components/resource/animation.cpp000066400000000000000000000017671413061077700231320ustar00rootroot00000000000000#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.47.0/components/resource/animation.hpp000066400000000000000000000020311413061077700231200ustar00rootroot00000000000000#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.47.0/components/resource/bulletshape.cpp000066400000000000000000000067431413061077700234620ustar00rootroot00000000000000#include "bulletshape.hpp" #include #include #include #include #include #include namespace Resource { BulletShape::BulletShape() : mCollisionShape(nullptr) , mAvoidCollisionShape(nullptr) { } BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) { } BulletShape::~BulletShape() { deleteShape(mAvoidCollisionShape); deleteShape(mCollisionShape); } void BulletShape::deleteShape(btCollisionShape* shape) { if(shape!=nullptr) { if(shape->isCompound()) { btCompoundShape* ms = static_cast(shape); int a = ms->getNumChildShapes(); for(int i=0; i getChildShape(i)); } delete shape; } } btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *shape) const { if(shape->isCompound()) { const btCompoundShape *comp = static_cast(shape); btCompoundShape *newShape = new btCompoundShape; int numShapes = comp->getNumChildShapes(); for(int i = 0;i < numShapes;++i) { btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); btTransform trans = comp->getChildTransform(i); newShape->addChildShape(trans, child); } return newShape; } if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) { btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); return newShape; } if (const btBoxShape* boxshape = dynamic_cast(shape)) { return new btBoxShape(*boxshape); } if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) return new btHeightfieldTerrainShape(static_cast(*shape)); throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); } btCollisionShape *BulletShape::getCollisionShape() const { return mCollisionShape; } btCollisionShape *BulletShape::getAvoidCollisionShape() const { return mAvoidCollisionShape; } void BulletShape::setLocalScaling(const btVector3& scale) { mCollisionShape->setLocalScaling(scale); if (mAvoidCollisionShape) mAvoidCollisionShape->setLocalScaling(scale); } osg::ref_ptr BulletShape::makeInstance() const { osg::ref_ptr instance (new BulletShapeInstance(this)); return instance; } BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : BulletShape() , mSource(source) { mCollisionBox = source->mCollisionBox; mAnimatedShapes = source->mAnimatedShapes; if (source->mCollisionShape) mCollisionShape = duplicateCollisionShape(source->mCollisionShape); if (source->mAvoidCollisionShape) mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape); } } openmw-openmw-0.47.0/components/resource/bulletshape.hpp000066400000000000000000000053311413061077700234570ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #include #include #include #include #include class btCollisionShape; namespace Resource { class BulletShapeInstance; class BulletShape : public osg::Object { public: BulletShape(); BulletShape(const BulletShape& copy, const osg::CopyOp& copyop); virtual ~BulletShape(); META_Object(Resource, BulletShape) btCollisionShape* mCollisionShape; btCollisionShape* mAvoidCollisionShape; struct CollisionBox { osg::Vec3f extents; osg::Vec3f center; }; // 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; osg::ref_ptr makeInstance() const; btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const; btCollisionShape* getCollisionShape() const; btCollisionShape* getAvoidCollisionShape() const; void setLocalScaling(const btVector3& scale); private: void deleteShape(btCollisionShape* shape); }; // 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); private: osg::ref_ptr mSource; }; // 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.47.0/components/resource/bulletshapemanager.cpp000066400000000000000000000157021413061077700250100ustar00rootroot00000000000000#include "bulletshapemanager.hpp" #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()); } #if OSG_MIN_VERSION_REQUIRED(3,5,6) void inline operator()( const osg::Vec3 v1, const osg::Vec3 v2, const osg::Vec3 v3 ) #else void inline operator()( const osg::Vec3 v1, const osg::Vec3 v2, const osg::Vec3 v3, bool _temp ) #endif { 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); btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, (aabbMax[1] + aabbMin[1]) / 2.0f, (aabbMax[2] + aabbMin[2]) / 2.0f ); shape->mCollisionShape = triangleMeshShape; 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) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) shape = osg::ref_ptr(static_cast(obj.get())); else { size_t extPos = normalized.find_last_of('.'); std::string ext; if (extPos != std::string::npos && extPos+1 < normalized.size()) ext = normalized.substr(extPos+1); if (ext == "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(); } } mCache->addEntryToObjectCache(normalized, shape); } return shape; } osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr instance = createInstance(normalized); if (instance) mInstanceCache->addEntryToObjectCache(normalized, instance.get()); return instance; } osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); 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 shape->makeInstance(); else 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.47.0/components/resource/bulletshapemanager.hpp000066400000000000000000000037251413061077700250170ustar00rootroot00000000000000#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; class 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.47.0/components/resource/imagemanager.cpp000066400000000000000000000165031413061077700235620ustar00rootroot00000000000000#include "imagemanager.hpp" #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) { std::string normalized = filename; mVFS->normalizeFilename(normalized); 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.c_str()); } catch (std::exception& e) { Log(Debug::Error) << "Failed to open image: " << e.what(); mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } size_t extPos = normalized.find_last_of('.'); std::string ext; if (extPos != std::string::npos && extPos+1 < normalized.size()) ext = normalized.substr(extPos+1); 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, 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.47.0/components/resource/imagemanager.hpp000066400000000000000000000020411413061077700235570ustar00rootroot00000000000000#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); osg::Image* getWarningImage(); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; ImageManager(const ImageManager&); void operator = (const ImageManager&); }; } #endif openmw-openmw-0.47.0/components/resource/keyframemanager.cpp000066400000000000000000000160271413061077700243040ustar00rootroot00000000000000#include "keyframemanager.hpp" #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 (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; 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) { std::string normalized = name; mVFS->normalizeFilename(normalized); 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); std::string ext = Resource::getFileExtension(normalized); if (ext == "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.47.0/components/resource/keyframemanager.hpp000066400000000000000000000034701413061077700243070ustar00rootroot00000000000000#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.47.0/components/resource/multiobjectcache.cpp000066400000000000000000000050271413061077700244510ustar00rootroot00000000000000#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.47.0/components/resource/multiobjectcache.hpp000066400000000000000000000023511413061077700244530ustar00rootroot00000000000000#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.47.0/components/resource/niffilemanager.cpp000066400000000000000000000025701413061077700241130ustar00rootroot00000000000000#include "niffilemanager.hpp" #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.47.0/components/resource/niffilemanager.hpp000066400000000000000000000015631413061077700241210ustar00rootroot00000000000000#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.47.0/components/resource/objectcache.hpp000066400000000000000000000172521413061077700234060ustar00rootroot00000000000000// 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.47.0/components/resource/resourcemanager.hpp000066400000000000000000000046151413061077700243350ustar00rootroot00000000000000#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; } 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.47.0/components/resource/resourcesystem.cpp000066400000000000000000000066261413061077700242460ustar00rootroot00000000000000#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.reset(new NifFileManager(vfs)); mImageManager.reset(new ImageManager(vfs)); mSceneManager.reset(new SceneManager(vfs, mImageManager.get(), mNifFileManager.get())); mKeyframeManager.reset(new KeyframeManager(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.47.0/components/resource/resourcesystem.hpp000066400000000000000000000054551413061077700242520ustar00rootroot00000000000000#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.47.0/components/resource/scenemanager.cpp000066400000000000000000000746111413061077700236010ustar00rootroot00000000000000#include "scenemanager.hpp" #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" #include "multiobjectcache.hpp" namespace { class InitWorldSpaceParticlesCallback : public osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgParticle::ParticleSystem* partsys = static_cast(node); // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) transformInitialParticles(partsys, partsys->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; }; 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) , mInstanceCache(new MultiObjectCache) , 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 translucentFramebuffer, bool forceShadersForNode) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); 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; } SceneUtil::LightingMethod SceneManager::getLightingMethod() const { return mLightingMethod; } void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; } 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) { std::string normalized = name; mVFS->normalizeFilename(normalized); return mCache->checkInObjectCache(normalized, 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 { try { return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { return osgDB::ReaderWriter::ReadResult(e.what()); } } private: Resource::ImageManager* mImageManager; }; osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { std::string ext = Resource::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(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"); osgDB::ReaderWriter::ReadResult result = reader->readNode(*vfs->get(normalizedFilename), 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"); result.getNode()->accept(nameFinder); if (nameFinder.mFoundNode) nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); return result.getNode(); } } class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: bool isReservedName(const std::string& name) const { if (name.empty()) return false; static std::vector reservedNames; if (reservedNames.empty()) { const char* reserved[] = {"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"}; reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); for (unsigned int i=0; i::iterator it = Misc::StringUtils::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 = name; mVFS->normalizeFilename(normalized); 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); } catch (std::exception& e) { static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; for (unsigned int i=0; iexists(normalized)) { Log(Debug::Error) << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead"; loaded = load(normalized, mVFS, mImageManager, mNifFileManager); break; } } if (!loaded) throw; } // set filtering settings SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsVisitor); SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); // share state // do this before optimizing so the optimizer will be able to combine nodes more aggressively // note, because StateSets will be shared at this point, StateSets can not be modified inside the optimizer mSharedStateMutex.lock(); mSharedStateManager->share(loaded.get()); mSharedStateMutex.unlock(); if (canOptimize(normalized)) { SceneUtil::Optimizer optimizer; optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); static const unsigned int options = getOptimizationOptions(); optimizer.optimize(loaded, options); } if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); else loaded->getBound(); mCache->addEntryToObjectCache(normalized, loaded); return loaded; } } osg::ref_ptr SceneManager::cacheInstance(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr node = createInstance(normalized); // Note: osg::clone() does not calculate bound volumes. // Do it immediately, otherwise we will need to update them for all objects // during first update traversal, what may lead to stuttering during cell transitions node->getBound(); mInstanceCache->addEntryToObjectCache(normalized, node.get()); return node; } osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); return createInstance(scene); } osg::ref_ptr SceneManager::createInstance(const osg::Node *base) { osg::ref_ptr cloned = static_cast(base->clone(SceneUtil::CopyOp())); // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(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) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) return static_cast(obj.get()); return createInstance(normalized); } 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); mInstanceCache->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); mInstanceCache->removeUnreferencedObjectsInCache(); 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(); mInstanceCache->clear(); } 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()); stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) { 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->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } std::string getFileExtension(const std::string& file) { size_t extPos = file.find_last_of('.'); if (extPos != std::string::npos && extPos+1 < file.size()) return file.substr(extPos+1); return std::string(); } } openmw-openmw-0.47.0/components/resource/scenemanager.hpp000066400000000000000000000216331413061077700236020ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H #define OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H #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; }; class MultiObjectCache; /// @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 translucentFramebuffer = false, bool forceShadersForNode = false); /// 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 setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; void setConvertAlphaTestToAlphaToCoverage(bool convert); 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); /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply /// return this cached object instead of creating a new one. /// @note The returned ref_ptr may be kept around by the caller to ensure that the object stays in cache for as long as needed. /// @note Thread safe. osg::ref_ptr cacheInstance(const std::string& name); osg::ref_ptr createInstance(const std::string& name); osg::ref_ptr createInstance(const osg::Node* base); void shareState(osg::ref_ptr node); /// Get an instance of the given scene template /// @see getTemplate /// @note Thread safe. osg::ref_ptr getInstance(const std::string& name); /// Get an instance of 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; private: Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); 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; osg::ref_ptr mInstanceCache; 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.47.0/components/resource/stats.cpp000066400000000000000000000355461413061077700223130ustar00rootroot00000000000000#include "stats.hpp" #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; 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); } } StatsHandler::StatsHandler(bool offlineCollect): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _font(""), _characterSize(18.0f) { _camera = new osg::Camera; _camera->getOrCreateStateSet()->setGlobalDefaults(); _camera->setRenderer(new osgViewer::Renderer(_camera.get())); _camera->setProjectionResizePolicy(osg::Camera::FIXED); _resourceStatsChildNum = 0; if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } Profiler::Profiler(bool offlineCollect): _offlineCollect(offlineCollect) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); else _font = ""; _characterSize = 18; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); setupStatCollection(); } bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { osgViewer::ViewerBase* viewer = nullptr; bool handled = StatsHandler::handle(ea, aa); 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", "UnrefQueue", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "", "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", "NavMesh UpdateJobs", "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", "NavMesh CacheHitRate", "", "Mechanics Actors", "Mechanics Objects", "", "Physics Actors", "Physics Objects", "Physics HeightFields", }); 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->setFont(_font); 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->setFont(_font); statsText->setCharacterSize(_characterSize); statsText->setPosition(pos); statsText->setText(""); statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); } } 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.47.0/components/resource/stats.hpp000066400000000000000000000032231413061077700223030ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_STATS_H #define OPENMW_COMPONENTS_RESOURCE_STATS_H #include namespace osgViewer { class ViewerBase; } namespace osg { class Switch; } namespace Resource { class Profiler : public osgViewer::StatsHandler { public: Profiler(bool offlineCollect); bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; private: bool _offlineCollect; }; class StatsHandler : public osgGA::GUIEventHandler { public: StatsHandler(bool offlineCollect); 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; std::string _font; float _characterSize; int _resourceStatsChildNum; }; void CollectStatistics(osgViewer::ViewerBase* viewer); } #endif openmw-openmw-0.47.0/components/sceneutil/000077500000000000000000000000001413061077700206005ustar00rootroot00000000000000openmw-openmw-0.47.0/components/sceneutil/actorutil.cpp000066400000000000000000000021461413061077700233150ustar00rootroot00000000000000#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.47.0/components/sceneutil/actorutil.hpp000066400000000000000000000003641413061077700233220ustar00rootroot00000000000000#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.47.0/components/sceneutil/agentpath.cpp000066400000000000000000000053121413061077700232600ustar00rootroot00000000000000#include "agentpath.hpp" #include "detourdebugdraw.hpp" #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 osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::Settings& settings) { using namespace DetourNavigator; const osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1); const auto agentRadius = halfExtents.x(); const auto agentHeight = 2.0f * halfExtents.z(); 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); return group; } } openmw-openmw-0.47.0/components/sceneutil/agentpath.hpp000066400000000000000000000007751413061077700232750ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H #define OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H #include #include namespace osg { class Group; class Vec3f; } namespace DetourNavigator { struct Settings; } namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::Settings& settings); } #endif openmw-openmw-0.47.0/components/sceneutil/attach.cpp000066400000000000000000000143411413061077700225530ustar00rootroot00000000000000#include "attach.hpp" #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; osg::Node* node = &drawable; while (node->getNumParents()) { osg::Group* parent = node->getParent(0); if (!parent || !filterMatches(parent->getName())) break; node = parent; } mToCopy.emplace(node); } void doCopy() { for (const osg::ref_ptr& node : mToCopy) { if (node->getNumParents() > 1) Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents"; while (node->getNumParents()) node->getParent(0)->removeChild(node); mParent->addChild(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(osg::UserDataContainer* source, osg::Object* target) { if (!target->getUserDataContainer()) target->setUserDataContainer(source); else { for (unsigned int i=0; igetNumUserObjects(); ++i) target->getUserDataContainer()->addUserObject(source->getUserObject(i)); } } osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) { if (dynamic_cast(toAttach.get())) { osg::ref_ptr handle = new osg::Group; CopyRigVisitor copyVisitor(handle, filter); toAttach->accept(copyVisitor); copyVisitor.doCopy(); 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); handle->setUserDataContainer(toAttach->getUserDataContainer()); return handle; } } else { FindByNameVisitor findBoneOffset("BoneOffset"); toAttach->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()); // The BoneOffset rotation seems to be incorrect trans->setAttitude(osg::Quat(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0))); // 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 (trans) { attachNode->addChild(trans); trans->addChild(toAttach); return trans; } else { attachNode->addChild(toAttach); return toAttach; } } } } openmw-openmw-0.47.0/components/sceneutil/attach.hpp000066400000000000000000000016031413061077700225550ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H #define OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H #include #include namespace osg { class Node; class Group; } namespace SceneUtil { /// 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); } #endif openmw-openmw-0.47.0/components/sceneutil/clone.cpp000066400000000000000000000073541413061077700224150ustar00rootroot00000000000000#include "clone.hpp" #include #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; } if (dynamic_cast(node) || dynamic_cast(node)) { return osg::clone(node, *this); } 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)) { 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.47.0/components/sceneutil/clone.hpp000066400000000000000000000027731413061077700224220ustar00rootroot00000000000000#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 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.47.0/components/sceneutil/controller.cpp000066400000000000000000000064731413061077700235010ustar00rootroot00000000000000#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); } 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.47.0/components/sceneutil/controller.hpp000066400000000000000000000063601413061077700235010ustar00rootroot00000000000000#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; private: std::shared_ptr mToAssign; }; /// 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.47.0/components/sceneutil/detourdebugdraw.cpp000066400000000000000000000070771413061077700245060ustar00rootroot00000000000000#include "detourdebugdraw.hpp" #include "util.hpp" #include #include #include #include namespace { using DetourNavigator::operator<<; 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::Vec3f& shift, float recastInvertedScaleFactor) : mGroup(group) , mShift(shift) , mRecastInvertedScaleFactor(recastInvertedScaleFactor) , mDepthMask(false) , mMode(osg::PrimitiveSet::POINTS) , mSize(1.0f) { } void DebugDraw::depthMask(bool state) { mDepthMask = state; } 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 * mRecastInvertedScaleFactor; } 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 stateSet(new osg::StateSet); stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateSet->setMode(GL_DEPTH, (mDepthMask ? osg::StateAttribute::ON : osg::StateAttribute::OFF)); stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateSet->setAttributeAndModes(new osg::LineWidth(mSize)); stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); osg::ref_ptr geometry(new osg::Geometry); geometry->setStateSet(stateSet); 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); } } openmw-openmw-0.47.0/components/sceneutil/detourdebugdraw.hpp000066400000000000000000000026071413061077700245050ustar00rootroot00000000000000#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: DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor); 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::Vec3f mShift; float mRecastInvertedScaleFactor; bool mDepthMask; 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.47.0/components/sceneutil/keyframe.hpp000066400000000000000000000037271413061077700231250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP #define OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP #include #include #include #include #include namespace SceneUtil { class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller { public: KeyframeController() {} KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , SceneUtil::Controller(copy) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) override { traverse(node, nodeVisitor); } }; /// 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.47.0/components/sceneutil/lightcontroller.cpp000066400000000000000000000044371413061077700245270ustar00rootroot00000000000000#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 ()(osg::Node* 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) { static_cast(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; } auto* lightSource = static_cast(node); lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * lightSource->getActorFade()); traverse(node, nv); } void LightController::setDiffuse(const osg::Vec4f& color) { mDiffuseColor = color; } } openmw-openmw-0.47.0/components/sceneutil/lightcontroller.hpp000066400000000000000000000016761413061077700245360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #include #include namespace SceneUtil { /// @brief Controller class to handle a pulsing and/or flickering light /// @note Must be set on a SceneUtil::LightSource. class LightController : public osg::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()(osg::Node* node, osg::NodeVisitor* nv) override; private: LightType mType; osg::Vec4f mDiffuseColor; float mPhase; float mBrightness; double mStartTime; double mLastTime; float mTicksToAdvance; }; } #endif openmw-openmw-0.47.0/components/sceneutil/lightmanager.cpp000066400000000000000000001477011413061077700237600ustar00rootroot00000000000000#include "lightmanager.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { 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; } float getLightRadius(const osg::Light* light) { float value = 0.0; light->getUserValue("radius", value); return value; } void setLightRadius(osg::Light* light, float value) { light->setUserValue("radius", value); } 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; } bool isReflectionCamera(osg::Camera* camera) { return (camera->getName() == "ReflectionCamera"); } } namespace SceneUtil { 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) { const Offsets offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride); // 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; } 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; }; osg::ref_ptr mData; osg::Endian mEndian; int mCount; Offsets mOffsets; osg::Vec4 mCachedSunPos; }; class LightStateCache { public: 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()); osg::ref_ptr uni = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", lightManager->getMaxLights()); uni->setElement(0, lightMat); stateset->addUniform(uni, 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); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); #else osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); #endif 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) {} osg::Object* cloneType() const override { return new DisableLight(mIndex); } osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } const char* libraryName() const override { return "SceneUtil"; } const char* className() const override { return "DisableLight"; } Type getType() const override { return 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(NifOsg, 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; }; 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; } class LightStateAttributePerObjectUniform : public osg::StateAttribute { public: LightStateAttributePerObjectUniform() : mLightManager(nullptr) {} LightStateAttributePerObjectUniform(const std::vector>& lights, LightManager* lightManager) : mLights(lights), mLightManager(lightManager) {} LightStateAttributePerObjectUniform(const LightStateAttributePerObjectUniform& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mLightManager(copy.mLightManager) {} int compare(const StateAttribute &sa) const override { throw std::runtime_error("LightStateAttributePerObjectUniform::compare: unimplemented"); } META_StateAttribute(NifOsg, LightStateAttributePerObjectUniform, osg::StateAttribute::LIGHT) void resize(int numLights) { mLights.resize(std::min(static_cast(numLights), mLights.size())); } void apply(osg::State &state) const override { osg::StateSet* stateSet = mLightManager->getStateSet(); if (!stateSet) return; auto* lightUniform = stateSet->getUniform("LightBuffer"); for (size_t i = 0; i < mLights.size(); ++i) { auto light = mLights[i]; osg::Matrixf lightMat; configurePosition(lightMat, light->getPosition() * state.getInitialViewMatrix()); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light)); lightUniform->setElement(i+1, lightMat); } auto sun = mLightManager->getSunlightBuffer(state.getFrameStamp()->getFrameNumber()); configurePosition(sun, osg::Vec4(sun(0,0), sun(0,1), sun(0,2), 0.0) * state.getInitialViewMatrix()); lightUniform->setElement(0, sun); lightUniform->dirty(); } private: std::vector> mLights; LightManager* mLightManager; }; 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) {} }; 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 indices = new osg::IntArray(mLightManager->getMaxLights()); osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size()); int pointCount = 0; for (size_t i = 0; i < lightList.size(); ++i) { int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()]; indices->at(pointCount++) = bufIndex; } indicesUni->setArray(indices); 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; std::vector> lights(lightList.size()); for (size_t i = 0; i < lightList.size(); ++i) { auto* light = lightList[i]->mLightSource->getLight(frameNum); lights[i] = light; setLightRadius(light, lightList[i]->mLightSource->getRadius()); } stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform(std::move(lights), mLightManager), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("PointLightCount", static_cast(lightList.size() + 1))); return stateset; } }; // 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 osg::NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , mLightManager(nullptr) { } META_Object(SceneUtil, SceneUtil::CollectLightCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override { 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 osg::NodeCallback { public: LightManagerUpdateCallback() { } LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) { } META_Object(SceneUtil, LightManagerUpdateCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override { LightManager* lightManager = static_cast(node); lightManager->update(nv->getTraversalNumber()); traverse(node, nv); } }; class LightManagerCullCallback : public osg::NodeCallback { public: LightManagerCullCallback(LightManager* lightManager) : mLightManager(lightManager), mLastFrameNumber(0) {} void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); if (mLastFrameNumber != cv->getTraversalNumber()) { mLastFrameNumber = cv->getTraversalNumber(); if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) { auto stateset = mLightManager->getStateSet(); auto bo = mLightManager->getLightBuffer(mLastFrameNumber); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); #else osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); #endif stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } auto sun = mLightManager->getSunlight(); if (sun) { // we must defer uploading the transformation to view-space position to deal with different cameras (e.g. reflection RTT). if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) { osg::Matrixf lightMat; configurePosition(lightMat, sun->getPosition()); configureAmbient(lightMat, sun->getAmbient()); configureDiffuse(lightMat, sun->getDiffuse()); configureSpecular(lightMat, sun->getSpecular()); mLightManager->setSunlightBuffer(lightMat, mLastFrameNumber); } else { auto buf = mLightManager->getLightBuffer(mLastFrameNumber); buf->setCachedSunPos(sun->getPosition()); buf->setAmbient(0, sun->getAmbient()); buf->setDiffuse(0, sun->getDiffuse()); buf->setSpecular(0, sun->getSpecular()); } } } traverse(node, nv); } private: LightManager* mLightManager; size_t mLastFrameNumber; }; class LightManagerStateAttribute : public osg::StateAttribute { public: LightManagerStateAttribute() : mLightManager(nullptr) , mInitLayout(false) { } LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) , mDummyProgram(new osg::Program) , mInitLayout(false) { static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); // 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(Shader::UBOBinding::LightBuffer)); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mLightManager(copy.mLightManager), mInitLayout(copy.mInitLayout) {} int compare(const StateAttribute &sa) const override { throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); } META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) void initSharedLayout(osg::GLExtensions* ext, int handle) const { constexpr std::array index = { static_cast(Shader::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()); for (int i = 0; i < 2; ++i) mLightManager->getLightBuffer(i)->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); } void apply(osg::State& state) const override { 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); mInitLayout = true; } } mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->uploadCachedSunPos(state.getInitialViewMatrix()); mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); } private: std::string 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; } LightManager* mLightManager; osg::ref_ptr mDummyProgram; mutable bool mInitLayout; }; const std::unordered_map LightManager::mLightingMethodSettingMap = { {"legacy", LightingMethod::FFP} ,{"shaders compatibility", LightingMethod::PerObjectUniform} ,{"shaders", LightingMethod::SingleUBO} }; LightingMethod LightManager::getLightingMethodFromString(const std::string& value) { auto it = LightManager::mLightingMethodSettingMap.find(value); if (it != LightManager::mLightingMethodSettingMap.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 : LightManager::mLightingMethodSettingMap) if (p.second == method) return p.first; return ""; } LightManager::~LightManager() { getOrCreateStateSet()->removeAttribute(osg::StateAttribute::LIGHT); } 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(mFFPMaxLights); 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; } int targetLights = std::clamp(Settings::Manager::getInt("max lights", "Shaders"), mMaxLightsLowerLimit, mMaxLightsUpperLimit); 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) { } 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; int targetLights = std::clamp(Settings::Manager::getInt("max lights", "Shaders"), mMaxLightsLowerLimit, mMaxLightsUpperLimit); setMaxLights(targetLights); if (getLightingMethod() == LightingMethod::PerObjectUniform) { auto* prevUniform = getStateSet()->getUniform("LightBuffer"); osg::ref_ptr newUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights()); for (int i = 0; i < getMaxLights(); ++i) { osg::Matrixf prevLightData; prevUniform->getElement(i, prevLightData); newUniform->setElement(i, prevLightData); } getStateSet()->removeUniform(prevUniform); getStateSet()->addUniform(newUniform); for (int i = 0; i < 2; ++i) { for (auto& pair : mStateSetCache[i]) static_cast(pair.second->getAttribute(osg::StateAttribute::LIGHT))->resize(getMaxLights()); mStateSetCache[i].clear(); } } else { for (int i = 0; i < 2; ++i) { for (auto& pair : mStateSetCache[i]) { auto& stateset = pair.second; osg::Uniform* uOldArray = stateset->getUniform("PointLightIndex"); osg::Uniform* uOldCount = stateset->getUniform("PointLightCount"); int prevCount; uOldCount->get(prevCount); int newCount = std::min(getMaxLights(), prevCount); uOldCount->set(newCount); osg::ref_ptr newArray = uOldArray->getIntArray(); newArray->resize(newCount); stateset->removeUniform(uOldArray); stateset->addUniform(new osg::Uniform("PointLightIndex", newArray)); } mStateSetCache[i].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) { auto* stateset = getOrCreateStateSet(); setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(targetLights); // ensures sunlight element in our uniform array is updated when there are no point lights in scene stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform({}, this), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights())); } void LightManager::initSingleUBO(int targetLights) { setLightingMethod(LightingMethod::SingleUBO); setMaxLights(targetLights); for (int i = 0; i < 2; ++i) { mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); osg::ref_ptr ubo = new osg::UniformBufferObject; ubo->setUsage(GL_STREAM_DRAW); mLightBuffers[i]->getData()->setBufferObject(ubo); } getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } 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) { 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; } osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; for (size_t i = 0; i < lightList.size(); ++i) { auto id = lightList[i]->mLightSource->getId(); Misc::hashCombine(hash, id); if (getLightingMethod() != LightingMethod::SingleUBO) continue; 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(lightList[i]->mLightSource->getId(), index); } auto& stateSetCache = mStateSetCache[frameNum%2]; auto found = stateSetCache.find(hash); if (found != stateSetCache.end()) { mStateSetGenerator->update(found->second, lightList, frameNum); return found->second; } auto stateset = mStateSetGenerator->generate(lightList, frameNum); stateSetCache.emplace(hash, stateset); return stateset; } const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { bool isReflection = isReflectionCamera(camera); 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 * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); if (!isReflection && mPointLightFadeEnd != 0.f) { const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); if (fade == 0.f) continue; auto* light = transform.mLightSource->getLight(frameNum); light->setDiffuse(light->getDiffuse() * fade); } LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; it->second.push_back(l); } } if (getLightingMethod() == LightingMethod::SingleUBO) { if (it->second.size() > static_cast(getMaxLightsInScene() - 1)) { 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() + 1, it->second.end(), sorter); it->second.erase((it->second.begin() + 1) + (getMaxLightsInScene() - 2), it->second.end()); } } return it->second; } void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum,const osg::RefMatrix* viewMatrix) { auto* light = lightSource->getLight(frameNum); auto& buf = 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)); } LightSource::LightSource() : mRadius(0.f) , mActorFade(1.f) { setUpdateCallback(new CollectLightCallback); mId = sLightId++; } LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op) : osg::Node(copy, copyop) , mRadius(copy.mRadius) , mActorFade(copy.mActorFade) { mId = sLightId++; for (int i = 0; i < 2; ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) { osgUtil::CullVisitor* cv = static_cast(nv); bool pushedState = pushLightState(node, cv); traverse(node, nv); 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: // - cull list of lights by the camera frustum // - organize lights in a quad tree // update light list if necessary // makes sure we don't update it more than once per frame when rendering with multiple cameras if (mLastFrameNumber != cv->getTraversalNumber()) { 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->getCurrentCamera(), 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::StateSet* stateset = nullptr; if (mLightList.size() > maxLights) { // remove lights culled by this camera LightManager::LightList lightList = mLightList; for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); osg::BoundingSphere bs = (*it)->mViewBound; bs._radius = bs._radius * 2.0; osg::CullingSet& cullingSet = stack.front(); if (cullingSet.isCulled(bs)) { it = lightList.erase(it); continue; } else ++it; } if (lightList.size() > maxLights) { // 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.47.0/components/sceneutil/lightmanager.hpp000066400000000000000000000244411413061077700237600ustar00rootroot00000000000000#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; 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 osg::ref_ptr mLight[2]; // LightSource will affect objects within this radius float mRadius; int mId; float mActorFade; 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; } /// 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; } }; /// @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); ~LightManager(); /// @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(osg::Camera* camera, 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& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } 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(); 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; // < Light list hash , StateSet > using LightStateSetMap = std::map>; LightStateSetMap mStateSetCache[2]; std::vector> mDummies; int mStartLight; size_t mLightingMask; osg::ref_ptr mSun; osg::ref_ptr mLightBuffers[2]; osg::Matrixf mSunlightBuffers[2]; // < Light ID , Buffer Index > using LightIndexMap = std::unordered_map; LightIndexMap mLightIndexMaps[2]; std::unique_ptr mStateSetGenerator; LightingMethod mLightingMethod; float mPointLightRadiusMultiplier; float mPointLightFadeEnd; float mPointLightFadeStart; int mMaxLights; SupportedMethods mSupported; static constexpr auto mMaxLightsLowerLimit = 2; static constexpr auto mMaxLightsUpperLimit = 64; static constexpr auto mFFPMaxLights = 8; static const std::unordered_map mLightingMethodSettingMap; }; /// 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 osg::NodeCallback { public: LightListCallback() : mLightManager(nullptr) , mLastFrameNumber(0) {} LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) , mLightManager(copy.mLightManager) , mLastFrameNumber(0) , mIgnoredLightSources(copy.mIgnoredLightSources) {} META_Object(SceneUtil, LightListCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override; 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.47.0/components/sceneutil/lightutil.cpp000066400000000000000000000125101413061077700233100ustar00rootroot00000000000000#include "lightutil.hpp" #include #include #include #include #include #include "lightmanager.hpp" #include "lightcontroller.hpp" #include "util.hpp" #include "visitor.hpp" #include "positionattitudetransform.hpp" 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 partsysMask, unsigned int lightMask, bool isExterior) { SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); osg::Group* attachTo = nullptr; if (visitor.mFoundNode) { attachTo = visitor.mFoundNode; } else { osg::ComputeBoundsVisitor computeBound; computeBound.setTraversalMask(~partsysMask); // We want the bounds of all children of the node, ignoring the node's local transformation // So do a traverse(), not accept() computeBound.traverse(*node); // PositionAttitudeTransform seems to be slightly faster than MatrixTransform osg::ref_ptr trans(new SceneUtil::PositionAttitudeTransform); trans->setPosition(computeBound.getBoundingBox().center()); node->addChild(trans); attachTo = trans; } osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior, osg::Vec4f(0,0,0,1)); attachTo->addChild(lightSource); 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.47.0/components/sceneutil/lightutil.hpp000066400000000000000000000040051413061077700233150ustar00rootroot00000000000000#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 added in the center of the node's bounds. /// @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 partsysMask Node mask to ignore when computing the sub graph's bounding box. /// @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 partsysMask, 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.47.0/components/sceneutil/morphgeometry.cpp000066400000000000000000000135261413061077700242140ustar00rootroot00000000000000#include "morphgeometry.hpp" #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) { mSourceGeometry = sourceGeom; for (unsigned int i=0; i<2; ++i) { mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); 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) cull(&nv); 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=0; i 0) { anyMorphTarget = true; break; } // before the MorphGeometry has started animating, we will use a regular bounding box (this is required // for correct object placements, which uses the bounding box) if (!mMorphedBoundingBox && !anyMorphTarget) { return mSourceGeometry->getBoundingBox(); } // once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating else { mMorphedBoundingBox = true; osg::Vec3Array& sourceVerts = *static_cast(mSourceGeometry->getVertexArray()); 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) { 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 = static_cast(mSourceGeometry->getVertexArray()); 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=0; isize(); ++vertex) (*positionDst)[vertex] += (*offsets)[vertex] * weight; } positionDst->dirty(); #if OSG_MIN_VERSION_REQUIRED(3, 5, 6) geom.dirtyGLObjects(); #endif nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); } osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const { return mGeometry[frame%2]; } } openmw-openmw-0.47.0/components/sceneutil/morphgeometry.hpp000066400000000000000000000064331413061077700242200ustar00rootroot00000000000000#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.47.0/components/sceneutil/mwshadowtechnique.cpp000066400000000000000000003667021413061077700250610ustar00rootroot00000000000000/* 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 "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 static osg::ref_ptr ss; if (!ss) { ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } cv->pushStateSet(ss); 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::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); pushViewport(viewport); pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); } 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::Drawable& drawable) { if (isCulled(drawable)) return; // push the culling mode. pushCurrentMask(); updateBound(drawable.getBoundingBox()); // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(Terrain::QuadTreeWorld & quadTreeWorld) { // For now, just expand the bounds fully as terrain will fill them up and possible ways to detect which terrain definitely won't cast shadows aren't implemented. update(osg::Vec3(-1.0, -1.0, 0.0)); update(osg::Vec3(1.0, 1.0, 0.0)); } 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::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); transform.computeLocalToWorldMatrix(*matrix, this); pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); traverse(transform); popModelViewMatrix(); } // pop the culling mode. popCurrentMask(); } 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): 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() { } 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 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 "< 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()!=0xffffffff) { // osg::ElapsedTime timer; osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); 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); // 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 (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); } } 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); _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(Vertices::iterator itr = intersections.begin(); itr != intersections.end(); ++itr) { center += *itr; 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(Vertices::iterator itr = intersections.begin(); itr != intersections.end(); ++itr) { osg::Vec3d dv = (*itr-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")); } openmw-openmw-0.47.0/components/sceneutil/mwshadowtechnique.hpp000066400000000000000000000260641413061077700250600ustar00rootroot00000000000000/* 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(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; void apply(Terrain::QuadTreeWorld& quadTreeWorld); void apply(osg::Billboard&) override; void apply(osg::Projection&) override; void apply(osg::Transform& transform) override; void apply(osg::Camera&) override; using osg::NodeVisitor::apply; void updateBound(const osg::BoundingBox& bb); void update(const osg::Vec3& v); osg::BoundingBox _bb; }; struct Frustum { Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar); osg::Matrixd projectionMatrix; osg::Matrixd modelViewMatrix; 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; }; // 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; protected: virtual ~ViewDependentData() {} MWShadowTechnique* _viewDependentShadowMap; std::array, 2> _stateset; LightDataList _lightDataList; ShadowDataList _shadowDataList; }; virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); virtual void createShaders(); virtual std::array, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; } 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; protected: virtual ~MWShadowTechnique(); typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; mutable std::mutex _viewDependentDataMapMutex; ViewDependentDataMap _viewDependentDataMap; 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; 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; }; } #endif openmw-openmw-0.47.0/components/sceneutil/navmesh.cpp000066400000000000000000000014031413061077700227430ustar00rootroot00000000000000#include "navmesh.hpp" #include "detourdebugdraw.hpp" #include #include #include namespace SceneUtil { osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings) { const osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); duDebugDrawNavMeshWithClosedList(&debugDraw, navMesh, navMeshQuery, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); return group; } } openmw-openmw-0.47.0/components/sceneutil/navmesh.hpp000066400000000000000000000005651413061077700227600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H #define OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H #include class dtNavMesh; namespace osg { class Group; } namespace DetourNavigator { struct Settings; } namespace SceneUtil { osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings); } #endif openmw-openmw-0.47.0/components/sceneutil/optimizer.cpp000066400000000000000000002133461413061077700233370ustar00rootroot00000000000000/* -*-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 #include #include "optimizer.hpp" #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 & 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<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) {} void add(osg::Transform* transform, bool canOptimize) { if (transform) { if (!canOptimize) _moreThanOneMatrixRequired=true; else if (transform->getReferenceFrame()!=osg::Transform::RELATIVE_RF) _moreThanOneMatrixRequired=true; else { if (_transformSet.empty()) transform->computeLocalToWorldMatrix(_firstMatrix,0); else { osg::Matrix matrix; transform->computeLocalToWorldMatrix(matrix,0); if (_firstMatrix!=matrix) _moreThanOneMatrixRequired=true; } } } else { if (!_transformSet.empty()) { if (!_firstMatrix.isIdentity()) _moreThanOneMatrixRequired=true; } } _transformSet.insert(transform); } bool _canBeApplied; bool _moreThanOneMatrixRequired; osg::Matrix _firstMatrix; TransformSet _transformSet; }; void registerWithCurrentObjects(osg::Transform* 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()<getUseVertexBufferObjects(); #endif } osg::Array* cloneArray(osg::Array* array, osg::VertexBufferObject*& vbo, const osg::Geometry* geom) { array = static_cast(array->clone(osg::CopyOp::DEEP_COPY_ALL)); if (!vbo && needvbo(geom)) vbo = new osg::VertexBufferObject; if (vbo) array->setVertexBufferObject(vbo); return array; } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) { osg::Geometry *geometry = drawable.asGeometry(); if((geometry) && (isOperationPermissibleForObject(&drawable))) { 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(&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(); } 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)->asTransform()->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)) { 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) { osg::Node* child = group->getChild(i); (*pitr)->insertChild(childIndex++, child); } (*pitr)->removeChild(group); } 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; } void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { _stateSetStack.push_back(stateSet); checkAlphaBlendingActive(); } 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) { if (group.getStateSet()) pushStateSet(group.getStateSet()); if (!_alphaBlendingActive || _mergeAlphaBlending) mergeGroup(group); traverse(group); if (group.getStateSet()) 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 && needvbo(geom)) ebo = new osg::ElementBufferObject; if (ebo) 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::Depth* d = new osg::Depth; d->setWriteMask(0); geom->getOrCreateStateSet()->setAttribute(d); } } } } // geode.dirtyBound(); 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 (needvbo(&lhs)) { 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 (needvbo(&lhs)) { 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 (needvbo(&lhs)) { 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() && isOperationPermissibleForObject(&node) && typeid(node)==typeid(osg::Group); } 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::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.47.0/components/sceneutil/optimizer.hpp000066400000000000000000000451561413061077700233460ustar00rootroot00000000000000/* -*-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 //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) {} 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; } /** 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; 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& geode) override; void apply(osg::Drawable& drawable) override; void apply(osg::Billboard& geode) override; void apply(osg::Transform& 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; 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 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; 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::Group& group) override; void apply(osg::LOD& lod) override; void apply(osg::Switch& switchNode) 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; } void pushStateSet(osg::StateSet* stateSet); void popStateSet(); void checkAlphaBlendingActive(); 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.47.0/components/sceneutil/osgacontroller.cpp000066400000000000000000000157501413061077700243510ustar00rootroot00000000000000#include #include #include #include #include #include #include #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 (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::handle_stateset(osg::StateSet* stateset) { if (!stateset) return; const osg::StateSet::AttributeList& attributeList = stateset->getAttributeList(); for (auto attribute : attributeList) { osg::StateAttribute* sattr = attribute.second.first.get(); osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(sattr->getUpdateCallback()); //Can this even be in sa? if (umt) link(umt); } } void LinkVisitor::setAnimation(Resource::Animation* animation) { mAnimation = animation; } void LinkVisitor::apply(osg::Node& node) { osg::StateSet* st = node.getStateSet(); if (st) handle_stateset(st); 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(); } traverse( node ); } void LinkVisitor::apply(osg::Geode& node) { for (unsigned int i = 0; i < node.getNumDrawables(); i++) { osg::Drawable* drawable = node.getDrawable(i); if (drawable && drawable->getStateSet()) handle_stateset(drawable->getStateSet()); } apply(static_cast(node)); } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(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, 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(std::vector emulatedAnimations) { mEmulatedAnimations = emulatedAnimations; } void OsgAnimationController::addMergedAnimationTrack(osg::ref_ptr animationTrack) { mMergedAnimationTracks.emplace_back(animationTrack); } } openmw-openmw-0.47.0/components/sceneutil/osgacontroller.hpp000066400000000000000000000047231413061077700243540ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP #define OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP #include #include #include #include #include #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 handle_stateset(osg::StateSet* stateset); virtual void setAnimation(Resource::Animation* animation); virtual void apply(osg::Node& node) override; virtual void apply(osg::Geode& node) override; protected: Resource::Animation* mAnimation; }; class OsgAnimationController : public SceneUtil::KeyframeController { public: /// @brief Handles the animation for osgAnimation formats OsgAnimationController() {}; OsgAnimationController(const OsgAnimationController& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, OsgAnimationController) /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; /// @brief Calls animation track update() void update(float time, std::string animationName); /// @brief Called every frame for osgAnimation void operator() (osg::Node*, osg::NodeVisitor*) override; /// @brief Sets details of the animations void setEmulatedAnimations(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; }; } #endif openmw-openmw-0.47.0/components/sceneutil/pathgridutil.cpp000066400000000000000000000223741413061077700240140ustar00rootroot00000000000000#include "pathgridutil.hpp" #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); } 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.47.0/components/sceneutil/pathgridutil.hpp000066400000000000000000000011151413061077700240070ustar00rootroot00000000000000#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.47.0/components/sceneutil/positionattitudetransform.cpp000066400000000000000000000023741413061077700266560ustar00rootroot00000000000000#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.47.0/components/sceneutil/positionattitudetransform.hpp000066400000000000000000000031171413061077700266570ustar00rootroot00000000000000#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.47.0/components/sceneutil/recastmesh.cpp000066400000000000000000000035431413061077700234470ustar00rootroot00000000000000#include "navmesh.hpp" #include "detourdebugdraw.hpp" #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::Settings& settings) { const osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1.0f / settings.mRecastScaleFactor); const auto normals = calculateNormals(recastMesh.getVertices(), recastMesh.getIndices()); const auto texScale = 1.0f / (settings.mCellSize * 10.0f); duDebugDrawTriMesh(&debugDraw, recastMesh.getVertices().data(), recastMesh.getVerticesCount(), recastMesh.getIndices().data(), normals.data(), recastMesh.getTrianglesCount(), nullptr, texScale); return group; } } openmw-openmw-0.47.0/components/sceneutil/recastmesh.hpp000066400000000000000000000006371413061077700234550ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H #define OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H #include namespace osg { class Group; } namespace DetourNavigator { class RecastMesh; struct Settings; } namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, const DetourNavigator::Settings& settings); } #endif openmw-openmw-0.47.0/components/sceneutil/riggeometry.cpp000066400000000000000000000304031413061077700236410ustar00rootroot00000000000000#include "riggeometry.hpp" #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) { mSourceGeometry = sourceGeometry; for (unsigned int i=0; i<2; ++i) { const osg::Geometry& from = *sourceGeometry; mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); 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(); it != path.rend(); ++it) { osg::Node* node = *it; 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(); #if OSG_MIN_VERSION_REQUIRED(3, 5, 6) geom.dirtyGLObjects(); #endif 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::ref_ptr geomToSkelMatrix; for (osg::NodePath::const_iterator it = nodePath.begin(); it != nodePath.end(); ++it) { osg::Node* node = *it; if (!foundSkel) { if (node == mSkeleton) foundSkel = true; } else { if (osg::Transform* trans = node->asTransform()) { if (!geomToSkelMatrix) geomToSkelMatrix = new osg::RefMatrix; trans->computeWorldToLocalMatrix(*geomToSkelMatrix, nullptr); } } } if (geomToSkelMatrix && !geomToSkelMatrix->isIdentity()) mGeomToSkelMatrix = geomToSkelMatrix; } 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) cull(&nv); 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.47.0/components/sceneutil/riggeometry.hpp000066400000000000000000000100541413061077700236460ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H #define OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H #include #include namespace SceneUtil { class Skeleton; class Bone; /// @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.47.0/components/sceneutil/serialize.cpp000066400000000000000000000142221413061077700232740ustar00rootroot00000000000000#include "serialize.hpp" #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 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::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 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::LightListCallback", "SceneUtil::LightManagerUpdateCallback", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", "SceneUtil::StateSetUpdater", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", "Shader::RemovedAlphaFunc", "NifOsg::LightManagerStateAttribute", "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.47.0/components/sceneutil/serialize.hpp000066400000000000000000000003761413061077700233060ustar00rootroot00000000000000#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.47.0/components/sceneutil/shadow.cpp000066400000000000000000000204621413061077700225750ustar00rootroot00000000000000#include "shadow.hpp" #include #include #include 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); int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 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::min(std::max(0.f, Settings::Manager::getFloat("shadow fade start", "Shadows")), 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; int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 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->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, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), mShadowTechnique(new MWShadowTechnique), mOutdoorShadowCastingMask(outdoorShadowCastingMask), mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); mShadowedScene->addChild(sceneRoot); rootNode->addChild(mShadowedScene); mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); setupShadowSettings(); mShadowTechnique->setupCastingShader(shaderManager); enableOutdoorMode(); } 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.47.0/components/sceneutil/shadow.hpp000066400000000000000000000022741413061077700226030ustar00rootroot00000000000000#ifndef COMPONENTS_SCENEUTIL_SHADOW_H #define COMPONENTS_SCENEUTIL_SHADOW_H #include #include #include #include "mwshadowtechnique.hpp" namespace SceneUtil { 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, Shader::ShaderManager &shaderManager); 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.47.0/components/sceneutil/shadowsbin.cpp000066400000000000000000000210621413061077700234460ustar00rootroot00000000000000#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 { std::array, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; ShadowsBin::ShadowsBin() { 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->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) { mAlphaFuncShaders[i] = new osg::StateSet; mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[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; } void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms) { sCastingPrograms = castingPrograms; osg::ref_ptr bin(new ShadowsBin); osgUtil::RenderBin::addRenderBinPrototype(name, bin); } 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 = newList; } } openmw-openmw-0.47.0/components/sceneutil/shadowsbin.hpp000066400000000000000000000052661413061077700234630ustar00rootroot00000000000000#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 { private: static std::array, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms; osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; std::array, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); 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); static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; class ShadowsBinAdder { public: ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } #endif openmw-openmw-0.47.0/components/sceneutil/skeleton.cpp000066400000000000000000000106721413061077700231360ustar00rootroot00000000000000#include "skeleton.hpp" #include #include #include #include namespace SceneUtil { class InitBoneCacheVisitor : public osg::NodeVisitor { public: InitBoneCacheVisitor(std::map >& cache) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCache(cache) { } void apply(osg::Transform &node) override { osg::MatrixTransform* bone = node.asMatrixTransform(); if (!bone) return; mCache[Misc::StringUtils::lowerCase(bone->getName())] = std::make_pair(getNodePath(), bone); traverse(node); } private: std::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.reset(new Bone); } const osg::NodePath& path = found->second.first; Bone* bone = mRootBone.get(); for (osg::NodePath::const_iterator it = path.begin(); it != path.end(); ++it) { osg::MatrixTransform* matrixTransform = dynamic_cast(*it); if (!matrixTransform) continue; Bone* child = nullptr; for (unsigned int i=0; imChildren.size(); ++i) { if (bone->mChildren[i]->mNode == *it) { child = bone->mChildren[i]; break; } } if (!child) { child = new Bone; bone->mChildren.push_back(child); mNeedToUpdateBoneMatrices = true; } bone = child; bone->mNode = matrixTransform; } return bone; } void Skeleton::updateBoneMatrices(unsigned int traversalNumber) { if (traversalNumber != mLastFrameNumber) mNeedToUpdateBoneMatrices = true; mLastFrameNumber = traversalNumber; if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) { for (unsigned int i=0; imChildren.size(); ++i) mRootBone->mChildren[i]->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) { } Bone::~Bone() { for (unsigned int i=0; igetMatrix() * (*parentMatrixInSkeletonSpace); else mMatrixInSkeletonSpace = mNode->getMatrix(); for (unsigned int i=0; iupdate(&mMatrixInSkeletonSpace); } } } openmw-openmw-0.47.0/components/sceneutil/skeleton.hpp000066400000000000000000000052151413061077700231400ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_SKELETON_H #define OPENMW_COMPONENTS_NIFOSG_SKELETON_H #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(); ~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); private: Bone(const Bone&); void operator=(const Bone&); }; /// @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::map > BoneCache; BoneCache mBoneCache; bool mBoneCacheInit; bool mNeedToUpdateBoneMatrices; ActiveType mActive; unsigned int mLastFrameNumber; unsigned int mLastCullFrameNumber; }; } #endif openmw-openmw-0.47.0/components/sceneutil/statesetupdater.cpp000066400000000000000000000052441413061077700245320ustar00rootroot00000000000000#include "statesetupdater.hpp" #include #include #include namespace SceneUtil { void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) { bool isCullVisitor = nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (!mStateSets[0]) { for (int i=0; i<2; ++i) { if (!isCullVisitor) mStateSets[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 else mStateSets[i] = new osg::StateSet; setDefaults(mStateSets[i]); } } osg::ref_ptr stateset = mStateSets[nv->getTraversalNumber()%2]; apply(stateset, nv); if (!isCullVisitor) node->setStateSet(stateset); else static_cast(nv)->pushStateSet(stateset); traverse(node, nv); if (isCullVisitor) static_cast(nv)->popStateSet(); } void StateSetUpdater::reset() { mStateSets[0] = nullptr; mStateSets[1] = nullptr; } StateSetUpdater::StateSetUpdater() { } StateSetUpdater::StateSetUpdater(const StateSetUpdater ©, const osg::CopyOp ©op) : osg::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 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 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 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. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. class StateSetUpdater : public osg::NodeCallback { public: StateSetUpdater(); StateSetUpdater(const StateSetUpdater& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, StateSetUpdater) void operator()(osg::Node* node, osg::NodeVisitor* nv) override; /// 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) {} /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} protected: /// Reset mStateSets, forcing a setDefaults() on the next frame. Can be used to change the defaults if needed. void reset(); private: osg::ref_ptr mStateSets[2]; }; /// @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.47.0/components/sceneutil/textkeymap.hpp000066400000000000000000000041751413061077700235130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP #define OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP #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(const std::string &groupName) const { return std::find_if(mTextKeyByTime.begin(), mTextKeyByTime.end(), IsGroupStart{groupName}); } bool hasGroupStart(const std::string &groupName) const { return mGroups.count(groupName) > 0; } private: struct IsGroupStart { const std::string &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.47.0/components/sceneutil/unrefqueue.cpp000066400000000000000000000013131413061077700234660ustar00rootroot00000000000000#include "unrefqueue.hpp" //#include //#include namespace SceneUtil { void UnrefWorkItem::doWork() { mObjects.clear(); } UnrefQueue::UnrefQueue() { mWorkItem = new UnrefWorkItem; } void UnrefQueue::push(const osg::Referenced *obj) { mWorkItem->mObjects.emplace_back(obj); } void UnrefQueue::flush(SceneUtil::WorkQueue *workQueue) { if (mWorkItem->mObjects.empty()) return; workQueue->addWorkItem(mWorkItem, true); mWorkItem = new UnrefWorkItem; } unsigned int UnrefQueue::getNumItems() const { return mWorkItem->mObjects.size(); } } openmw-openmw-0.47.0/components/sceneutil/unrefqueue.hpp000066400000000000000000000022671413061077700235040ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H #define OPENMW_COMPONENTS_UNREFQUEUE_H #include #include #include #include namespace SceneUtil { class WorkQueue; class UnrefWorkItem : public SceneUtil::WorkItem { public: std::deque > mObjects; void doWork() override; }; /// @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 osg::Referenced { public: UnrefQueue(); /// Adds an object to the list of objects to be unreferenced. Call from the main thread. void push(const osg::Referenced* 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); unsigned int getNumItems() const; private: osg::ref_ptr mWorkItem; }; } #endif openmw-openmw-0.47.0/components/sceneutil/util.cpp000066400000000000000000000231161413061077700222640ustar00rootroot00000000000000#include "util.hpp" #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; } void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { osg::BoundingSphere::vec_type xdash = bsphere._center; xdash.x() += bsphere._radius; xdash = xdash*matrix; osg::BoundingSphere::vec_type ydash = bsphere._center; ydash.y() += bsphere._radius; ydash = ydash*matrix; osg::BoundingSphere::vec_type zdash = bsphere._center; zdash.z() += bsphere._radius; zdash = zdash*matrix; bsphere._center = bsphere._center*matrix; xdash -= bsphere._center; osg::BoundingSphere::value_type sqrlen_xdash = xdash.length2(); ydash -= bsphere._center; osg::BoundingSphere::value_type sqrlen_ydash = ydash.length2(); zdash -= bsphere._center; osg::BoundingSphere::value_type sqrlen_zdash = zdash.length2(); bsphere._radius = sqrlen_xdash; if (bsphere._radius> 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, 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) { #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 osg::ref_ptr extensions = osg::GLExtensions::Get(0, false); if (extensions) extensions->glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif 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; } } openmw-openmw-0.47.0/components/sceneutil/util.hpp000066400000000000000000000047211413061077700222720ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_UTIL_H #define OPENMW_COMPONENTS_SCENEUTIL_UTIL_H #include #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 void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere); osg::Vec4f colourFromRGB (unsigned int clr); osg::Vec4f colourFromRGBA (unsigned int value); float makeOsgColorComponent (unsigned int value, unsigned int shift); bool hasUserDescription(const osg::Node* node, const std::string pattern); osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, 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); } #endif openmw-openmw-0.47.0/components/sceneutil/visitor.cpp000066400000000000000000000114501413061077700230040ustar00rootroot00000000000000#include "visitor.hpp" #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(node.className(), mNameToFind)) mFoundNodes.push_back(&node); traverse(node); } void FindByNameVisitor::apply(osg::Group &group) { if (!checkGroup(group)) traverse(group); } void FindByNameVisitor::apply(osg::MatrixTransform &node) { if (!checkGroup(node)) traverse(node); } void FindByNameVisitor::apply(osg::Geometry&) { } void DisableFreezeOnCullVisitor::apply(osg::MatrixTransform &node) { traverse(node); } void DisableFreezeOnCullVisitor::apply(osg::Drawable& drw) { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) partsys->setFreezeOnCull(false); } void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Take transformation for first found node in file std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); if (trans.libraryName() == std::string("osgAnimation")) { // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) std::string underscore = "_"; std::size_t foundUnderscore = originalNodeName.find(underscore); if (foundUnderscore != std::string::npos) std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); } const std::string nodeName = originalNodeName; mMap.emplace(nodeName, &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.47.0/components/sceneutil/visitor.hpp000066400000000000000000000064311413061077700230140ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #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; }; // Disable freezeOnCull for all visited particlesystems class DisableFreezeOnCullVisitor : public osg::NodeVisitor { public: DisableFreezeOnCullVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::MatrixTransform& node) override; void apply(osg::Drawable& drw) override; }; /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: typedef std::map > 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.47.0/components/sceneutil/waterutil.cpp000066400000000000000000000070161413061077700233300ustar00rootroot00000000000000#include "waterutil.hpp" #include #include #include #include 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 osg::Depth); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); stateset->setRenderBinDetails(renderBin, "RenderBin"); // Let the shader know we're dealing with simple water here. stateset->addUniform(new osg::Uniform("simpleWater", true)); return stateset; } } openmw-openmw-0.47.0/components/sceneutil/waterutil.hpp000066400000000000000000000005751413061077700233400ustar00rootroot00000000000000#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.47.0/components/sceneutil/workqueue.cpp000066400000000000000000000046461413061077700233450ustar00rootroot00000000000000#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(int workerThreads) : mIsReleased(false) { for (int i=0; i(*this)); } WorkQueue::~WorkQueue() { { 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(item); else mQueue.push_back(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 = mQueue.front(); mQueue.pop_front(); return item; } else 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.47.0/components/sceneutil/workqueue.hpp000066400000000000000000000052511413061077700233430ustar00rootroot00000000000000#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(int numWorkerThreads=1); ~WorkQueue(); /// 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.47.0/components/sceneutil/writescene.cpp000066400000000000000000000013661413061077700234620ustar00rootroot00000000000000#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.47.0/components/sceneutil/writescene.hpp000066400000000000000000000004051413061077700234600ustar00rootroot00000000000000#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.47.0/components/sdlutil/000077500000000000000000000000001413061077700202655ustar00rootroot00000000000000openmw-openmw-0.47.0/components/sdlutil/events.hpp000066400000000000000000000042011413061077700222770ustar00rootroot00000000000000#ifndef _SFO_EVENTS_H #define _SFO_EVENTS_H #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; }; /////////////// // 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() {} /** @remarks Joystick button down event */ virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; /** @remarks Joystick button up event */ virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; /** @remarks Joystick axis moved event */ virtual void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) = 0; /** @remarks Joystick Added **/ virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0; /** @remarks Joystick Removed **/ virtual void controllerRemoved(const SDL_ControllerDeviceEvent &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.47.0/components/sdlutil/gl4es_init.cpp000066400000000000000000000014561413061077700230400ustar00rootroot00000000000000// 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.47.0/components/sdlutil/gl4es_init.h000066400000000000000000000006701413061077700225020ustar00rootroot00000000000000#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.47.0/components/sdlutil/imagetosurface.cpp000066400000000000000000000016251413061077700237730ustar00rootroot00000000000000#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.47.0/components/sdlutil/imagetosurface.hpp000066400000000000000000000006411413061077700237750ustar00rootroot00000000000000#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.47.0/components/sdlutil/sdlcursormanager.cpp000066400000000000000000000225571413061077700243570ustar00rootroot00000000000000#include "sdlcursormanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imagetosurface.hpp" #if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() #endif namespace CursorDecompression { // macOS builds use the OSG fork that includes DXTC commit #if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 8) || defined(__APPLE__) static const bool DXTCSupported = true; #else static const bool DXTCSupported = false; #endif class MyGraphicsContext { public: MyGraphicsContext(int w, int h) { osg::ref_ptr traits = new osg::GraphicsContext::Traits; traits->x = 0; traits->y = 0; traits->width = w; traits->height = h; traits->red = 8; traits->green = 8; traits->blue = 8; traits->alpha = 8; traits->windowDecoration = false; traits->doubleBuffer = false; traits->sharedContext = nullptr; traits->pbuffer = true; osg::GraphicsContext::ScreenIdentifier si; si.readDISPLAY(); if (si.displayNum<0) si.displayNum = 0; traits->displayNum = si.displayNum; traits->screenNum = si.screenNum; traits->hostName = si.hostName; _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); if (!_gc) { Log(Debug::Warning) << "Failed to create pbuffer, failing back to normal graphics window."; traits->pbuffer = false; _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); if (!_gc) throw std::runtime_error("Failed to create graphics context for image decompression"); } if (_gc.valid()) { _gc->realize(); _gc->makeCurrent(); } } osg::ref_ptr getContext() { return _gc; } bool valid() const { return _gc.valid() && _gc->isRealized(); } private: osg::ref_ptr _gc; }; SDLUtil::SurfaceUniquePtr hardwareDecompress (osg::ref_ptr source, float rotDegrees) { int width = source->s(); int height = source->t(); MyGraphicsContext context(width, height); osg::ref_ptr state = context.getContext()->getState(); osg::ref_ptr texture = new osg::Texture2D; texture->setImage(source); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); texture->setBorderColor(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); osg::ref_ptr texmat = new osg::TexMat; osg::Matrix texRot (osg::Matrix::identity()); float theta ( osg::DegreesToRadians(-rotDegrees) ); float cosTheta = std::cos(theta); float sinTheta = std::sin(theta); texRot(0,0) = cosTheta; texRot(1,0) = -sinTheta; texRot(0,1) = sinTheta; texRot(1,1) = cosTheta; // Offset center of rotation to center of texture texRot(3,0) = 0.5f + ( (-0.5f * cosTheta) - (-0.5f * sinTheta) ); texRot(3,1) = 0.5f + ( (-0.5f * sinTheta) + (-0.5f * cosTheta) ); texmat->setMatrix(texRot); state->applyTextureAttribute(0, texmat); osg::ref_ptr identity (new osg::RefMatrix(osg::Matrix::identity())); state->applyModelViewMatrix(identity); state->applyProjectionMatrix(identity); state->applyMode(GL_TEXTURE_2D, true); state->applyTextureAttribute(0, texture); osg::ref_ptr resultImage = new osg::Image; resultImage->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); osg::RenderInfo renderInfo; renderInfo.setState(state); glViewport(0, 0, width, height); osg::ref_ptr geom; geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,-1,0), osg::Vec3(2,0,0), osg::Vec3(0,2,0)); geom->drawImplementation(renderInfo); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, resultImage->data()); geom->releaseGLObjects(); source->releaseGLObjects(); texture->releaseGLObjects(); return SDLUtil::imageToSurface(resultImage, true); } SDLUtil::SurfaceUniquePtr softwareDecompress (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); } } 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 } 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; static bool forceSoftwareDecompression = (getenv("OPENMW_DECOMPRESS_TEXTURES") != nullptr); SurfaceUniquePtr (*decompressionFunction)(osg::ref_ptr, float); if (forceSoftwareDecompression || CursorDecompression::DXTCSupported) { decompressionFunction = CursorDecompression::softwareDecompress; } else { decompressionFunction = CursorDecompression::hardwareDecompress; } try { auto surface = decompressionFunction(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.47.0/components/sdlutil/sdlcursormanager.hpp000066400000000000000000000024261413061077700243550ustar00rootroot00000000000000#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.47.0/components/sdlutil/sdlgraphicswindow.cpp000066400000000000000000000146761413061077700245420ustar00rootroot00000000000000#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.47.0/components/sdlutil/sdlinputwrapper.cpp000066400000000000000000000341011413061077700242330ustar00rootroot00000000000000#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 keep others for later while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT)) handleWindowEvent(evt); SDL_FlushEvent(SDL_KEYDOWN); SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN); SDL_FlushEvent(SDL_MOUSEBUTTONDOWN); 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; 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.47.0/components/sdlutil/sdlinputwrapper.hpp000066400000000000000000000045261413061077700242500ustar00rootroot00000000000000#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.47.0/components/sdlutil/sdlvideowrapper.cpp000066400000000000000000000067031413061077700242110ustar00rootroot00000000000000#include "sdlvideowrapper.hpp" #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, bool fullscreen, bool windowBorder) { SDL_SetWindowFullscreen(mWindow, 0); if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED) SDL_RestoreWindow(mWindow); if (fullscreen) { SDL_DisplayMode mode; SDL_GetWindowDisplayMode(mWindow, &mode); mode.w = width; mode.h = height; SDL_SetWindowDisplayMode(mWindow, &mode); SDL_SetWindowFullscreen(mWindow, fullscreen); } 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.47.0/components/sdlutil/sdlvideowrapper.hpp000066400000000000000000000016431413061077700242140ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H #define OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H #include #include struct SDL_Window; namespace osgViewer { class Viewer; } 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, bool fullscreen, 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.47.0/components/settings/000077500000000000000000000000001413061077700204455ustar00rootroot00000000000000openmw-openmw-0.47.0/components/settings/categories.hpp000066400000000000000000000006701413061077700233060ustar00rootroot00000000000000#ifndef COMPONENTS_SETTINGS_CATEGORIES_H #define COMPONENTS_SETTINGS_CATEGORIES_H #include #include #include #include namespace Settings { using CategorySetting = std::pair; using CategorySettingVector = std::set>; using CategorySettingValueMap = std::map; } #endif // COMPONENTS_SETTINGS_CATEGORIES_H openmw-openmw-0.47.0/components/settings/parser.cpp000066400000000000000000000317551413061077700224600ustar00rootroot00000000000000#include "parser.hpp" #include #include #include #include #include void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64Encoded) { 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 (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(); } 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.47.0/components/settings/parser.hpp000066400000000000000000000014771413061077700224630ustar00rootroot00000000000000#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); 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); void fail(const std::string& message); std::string mFile; int mLine = 0; }; } #endif // _COMPONENTS_SETTINGS_PARSER_H openmw-openmw-0.47.0/components/settings/settings.cpp000066400000000000000000000122011413061077700230050ustar00rootroot00000000000000#include "settings.hpp" #include "parser.hpp" #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(); } void Manager::loadDefault(const std::string &file) { SettingsFileParser parser; parser.loadSettingsFile(file, mDefaultSettings, true); } void Manager::loadUser(const std::string &file) { SettingsFileParser parser; parser.loadSettingsFile(file, mUserSettings); } void Manager::saveUser(const std::string &file) { SettingsFileParser parser; parser.saveSettingsFile(file, mUserSettings); } std::string Manager::getString(const std::string &setting, const std::string &category) { CategorySettingValueMap::key_type 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; throw std::runtime_error(std::string("Trying to retrieve a non-existing setting: ") + setting + ".\nMake sure the defaults.bin file was properly installed."); } float Manager::getFloat (const std::string& setting, const std::string& category) { const std::string& value = getString(setting, category); std::stringstream stream(value); float number = 0.f; stream >> number; return number; } double Manager::getDouble (const std::string& setting, const std::string& category) { const std::string& value = getString(setting, category); std::stringstream stream(value); double number = 0.0; stream >> number; return number; } int Manager::getInt (const std::string& setting, const std::string& category) { const std::string& value = getString(setting, category); std::stringstream stream(value); int number = 0; stream >> number; return number; } bool Manager::getBool (const std::string& setting, const std::string& category) { const std::string& string = getString(setting, category); return Misc::StringUtils::ciEqual(string, "true"); } osg::Vec2f Manager::getVector2 (const std::string& setting, const std::string& 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 osg::Vec2f(x, y); } osg::Vec3f Manager::getVector3 (const std::string& setting, const std::string& 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 osg::Vec3f(x, y, z); } void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) { CategorySettingValueMap::key_type key = std::make_pair(category, setting); CategorySettingValueMap::iterator found = mUserSettings.find(key); if (found != mUserSettings.end()) { if (found->second == value) return; } mUserSettings[key] = value; mChangedSettings.insert(key); } void Manager::setInt (const std::string& setting, const std::string& category, const int value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setDouble (const std::string &setting, const std::string &category, const double value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { setString(setting, category, value ? "true" : "false"); } void Manager::setVector2 (const std::string &setting, const std::string &category, const osg::Vec2f value) { std::ostringstream stream; stream << value.x() << " " << value.y(); setString(setting, category, stream.str()); } void Manager::setVector3 (const std::string &setting, const std::string &category, const osg::Vec3f value) { std::ostringstream stream; stream << value.x() << ' ' << value.y() << ' ' << value.z(); setString(setting, category, stream.str()); } void Manager::resetPendingChange(const std::string &setting, const std::string &category) { CategorySettingValueMap::key_type key = std::make_pair(category, setting); mChangedSettings.erase(key); } const CategorySettingVector Manager::getPendingChanges() { return mChangedSettings; } void Manager::resetPendingChanges() { mChangedSettings.clear(); } } openmw-openmw-0.47.0/components/settings/settings.hpp000066400000000000000000000052001413061077700230130ustar00rootroot00000000000000#ifndef COMPONENTS_SETTINGS_H #define COMPONENTS_SETTINGS_H #include "categories.hpp" #include #include #include #include #include namespace Settings { /// /// \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 void clear(); ///< clears all settings and default settings void loadDefault (const std::string& file); ///< load file as the default settings (can be overridden by user settings) void loadUser (const std::string& file); ///< load file as user settings void saveUser (const std::string& file); ///< save user settings to file static void resetPendingChange(const std::string &setting, const std::string &category); static void resetPendingChanges(); static const CategorySettingVector getPendingChanges(); ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category); static double getDouble (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); static osg::Vec2f getVector2 (const std::string& setting, const std::string& category); static osg::Vec3f getVector3 (const std::string& setting, const std::string& category); static void setInt (const std::string& setting, const std::string& category, const int value); static void setFloat (const std::string& setting, const std::string& category, const float value); static void setDouble (const std::string& setting, const std::string& category, const double value); static void setString (const std::string& setting, const std::string& category, const std::string& value); static void setBool (const std::string& setting, const std::string& category, const bool value); static void setVector2 (const std::string& setting, const std::string& category, const osg::Vec2f value); static void setVector3 (const std::string& setting, const std::string& category, const osg::Vec3f value); }; } #endif // _COMPONENTS_SETTINGS_H openmw-openmw-0.47.0/components/shader/000077500000000000000000000000001413061077700200535ustar00rootroot00000000000000openmw-openmw-0.47.0/components/shader/removedalphafunc.cpp000066400000000000000000000012011413061077700240740ustar00rootroot00000000000000#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.47.0/components/shader/removedalphafunc.hpp000066400000000000000000000023701413061077700241110ustar00rootroot00000000000000#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.47.0/components/shader/shadermanager.cpp000066400000000000000000000417541413061077700233730ustar00rootroot00000000000000#include "shadermanager.hpp" #include #include #include #include #include #include #include #include #include namespace Shader { ShaderManager::ShaderManager() : mLightingMethod(SceneUtil::LightingMethod::FFP) { } void ShaderManager::setShaderPath(const std::string &path) { mPath = path; } void ShaderManager::setLightingMethod(SceneUtil::LightingMethod method) { mLightingMethod = method; } 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. // includingFiles 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(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set includingFiles) { // An include is cyclic if it is being included by itself if (includingFiles.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, includingFiles)) { 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 parseFors(std::string& source, const std::string& templateName) { const char escapeCharacter = '$'; size_t foundPos = 0; 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 command = source.substr(foundPos + 1, endPos - (foundPos + 1)); if (command != "foreach") { Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << command; return false; } 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; } 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 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 (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; } osg::ref_ptr ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) { std::lock_guard lock(mMutex); // read the template if we haven't already TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); 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, {})) return nullptr; 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; if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName)) { // 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 name to allow the SharedStateManager to compare shaders efficiently static unsigned int counter = 0; shader->setName(std::to_string(counter++)); 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) { std::lock_guard lock(mMutex); ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); if (found == mPrograms.end()) { osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); program->addBindAttribLocation("aOffset", 6); program->addBindAttribLocation("aRotation", 7); if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; } ShaderManager::DefineMap ShaderManager::getGlobalDefines() { return DefineMap(mGlobalDefines); } void ShaderManager::setGlobalDefines(DefineMap & globalDefines) { mGlobalDefines = globalDefines; for (auto shaderMapElement: mShaders) { std::string templateId = shaderMapElement.first.first; ShaderManager::DefineMap defines = shaderMapElement.first.second; osg::ref_ptr shader = shaderMapElement.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]; if (!parseDefines(shaderSource, defines, mGlobalDefines, templateId) || !parseFors(shaderSource, templateId)) // 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); } } void ShaderManager::releaseGLObjects(osg::State *state) { std::lock_guard lock(mMutex); for (auto shader : mShaders) { if (shader.second != nullptr) shader.second->releaseGLObjects(state); } for (auto program : mPrograms) program.second->releaseGLObjects(state); } } openmw-openmw-0.47.0/components/shader/shadermanager.hpp000066400000000000000000000057141413061077700233740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H #define OPENMW_COMPONENTS_SHADERMANAGER_H #include #include #include #include #include #include #include namespace Resource { class SceneManager; } namespace SceneUtil { enum class LightingMethod; } namespace Shader { enum class UBOBinding { LightBuffer }; /// @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: ShaderManager(); void setShaderPath(const std::string& path); void setLightingMethod(SceneUtil::LightingMethod method); 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); /// 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); private: 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; SceneUtil::LightingMethod mLightingMethod; std::mutex mMutex; }; bool parseFors(std::string& source, const std::string& templateName); bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName); } #endif openmw-openmw-0.47.0/components/shader/shadervisitor.cpp000066400000000000000000001050761413061077700234560ustar00rootroot00000000000000#include "shadervisitor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader { 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) { } 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()); } bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } const std::set& getAttributes() { return mAttributes; } bool empty() { return mUniforms.empty() && mModes.empty() && mAttributes.empty(); } META_Object(Shader, AddedState) private: class InterrogateModesHelper : public osg::StateAttribute::ModeUsage { public: InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } void usesTextureMode(osg::StateAttribute::GLMode mode) override {} private: AddedState* mTracker; }; std::unordered_set mUniforms; std::unordered_set mModes; std::set mAttributes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) , mAlphaTestOverridden(false) , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) { } ShaderVisitor::ShaderRequirements::~ShaderRequirements() { } 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) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { mRequirements.emplace_back(); } void ShaderVisitor::setForceShaders(bool force) { mForceShaders = force; } void ShaderVisitor::apply(osg::Node& node) { if (node.getStateSet()) { pushRequirements(node); applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); } else traverse(node); } 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" }; bool isTextureNameRecognized(const std::string& name) { for (unsigned int i=0; i 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; 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) { 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 (!mTranslucentFramebuffer) 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; } } if (diffuseMap) { if (!writableStateSet) writableStateSet = getWritableStateSet(node); // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. // Also it should probably belong to the shader manager or be applied by the shadows bin writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); osg::ref_ptr addedState = getAddedState(*stateset); 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(); } } } } 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) { 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; } 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); } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); // back up removed state in case recreateShaders gets rid of the shader later 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 (!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 } 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); } 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); } defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; 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); 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); } } } 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); // 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); } 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() != nullptr); if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { pushRequirements(geometry); 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) { // non-Geometry drawable (e.g. particle system) bool needPop = (drawable.getStateSet() != nullptr); if (drawable.getStateSet()) { pushRequirements(drawable); applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) { 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 ensureFFP(drawable); 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::setTranslucentFramebuffer(bool translucent) { mTranslucentFramebuffer = translucent; } ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) { } void ReinstateRemovedStateVisitor::apply(osg::Node& node) { 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); } } traverse(node); } } openmw-openmw-0.47.0/components/shader/shadervisitor.hpp000066400000000000000000000074421413061077700234610ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H #define OPENMW_COMPONENTS_SHADERVISITOR_H #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); /// 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 setTranslucentFramebuffer(bool translucent); 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 mTranslucentFramebuffer; ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; struct ShaderRequirements { ShaderRequirements(); ~ShaderRequirements(); // std::map mTextures; bool mShaderRequired; int mColorMode; bool mMaterialOverridden; bool mAlphaTestOverridden; bool mAlphaBlendOverridden; GLenum mAlphaFunc; float mAlphaRef; bool mAlphaBlend; bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required int mTexStageRequiringTangents; // 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); }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor { public: ReinstateRemovedStateVisitor(bool allowedToModifyStateSets); void apply(osg::Node& node) override; private: bool mAllowedToModifyStateSets; }; } #endif openmw-openmw-0.47.0/components/terrain/000077500000000000000000000000001413061077700202515ustar00rootroot00000000000000openmw-openmw-0.47.0/components/terrain/buffercache.cpp000066400000000000000000000216431413061077700232200ustar00rootroot00000000000000#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 size_t lodLevel = (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 (auto indexbuffer : mIndexBufferMap) indexbuffer.second->releaseGLObjects(state); } { std::lock_guard lock(mUvBufferMutex); for (auto uvbuffer : mUvBufferMap) uvbuffer.second->releaseGLObjects(state); } } } openmw-openmw-0.47.0/components/terrain/buffercache.hpp000066400000000000000000000024161413061077700232220ustar00rootroot00000000000000#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.47.0/components/terrain/cellborder.cpp000066400000000000000000000056071413061077700231020ustar00rootroot00000000000000#include "cellborder.hpp" #include #include #include #include #include "world.hpp" #include "../esm/loadland.hpp" namespace Terrain { CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask): mWorld(world), mRoot(root), mBorderMask(borderMask) { } void CellBorder::createCellBorderGeometry(int x, int y) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; const float offset = 10.0; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); 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 = cellSize / ((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(cellSize,(i - borderSegments) * borderStep,0.0f); pos += cellCorner; pos += osg::Vec3f(0,0,mWorld->getHeightAt(pos) + offset); vertices->push_back(pos); osg::Vec4f col = i % 2 == 0 ? osg::Vec4f(0,0,0,1) : osg::Vec4f(1,1,0,1); 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 borderGeode = new osg::Geode; borderGeode->addDrawable(border.get()); osg::StateSet *stateSet = borderGeode->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); borderGeode->setNodeMask(mBorderMask); mRoot->addChild(borderGeode); mCellBorderNodes[std::make_pair(x,y)] = borderGeode; } 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.47.0/components/terrain/cellborder.hpp000066400000000000000000000014101413061077700230730ustar00rootroot00000000000000#ifndef GAME_RENDER_CELLBORDER #define GAME_RENDER_CELLBORDER #include #include namespace Terrain { 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); void createCellBorderGeometry(int x, int y); void destroyCellBorderGeometry(int x, int y); /** Destroys the geometry for all borders. */ void destroyCellBorderGeometry(); protected: Terrain::World *mWorld; osg::Group *mRoot; CellGrid mCellBorderNodes; int mBorderMask; }; } #endif openmw-openmw-0.47.0/components/terrain/chunkmanager.cpp000066400000000000000000000230641413061077700234250ustar00rootroot00000000000000#include "chunkmanager.hpp" #include #include #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); } 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) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return obj->asNode(); else { osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); 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->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) { osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); osg::ref_ptr colors (new osg::Vec4ubArray); colors->setNormalize(true); osg::ref_ptr vbo (new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); osg::ref_ptr geometry (new TerrainDrawable); 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 (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->getShaderManager(), 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 (compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } geometry->setNodeMask(mNodeMask); return geometry; } } openmw-openmw-0.47.0/components/terrain/chunkmanager.hpp000066400000000000000000000050301413061077700234230ustar00rootroot00000000000000#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; 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); 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.47.0/components/terrain/compositemaprenderer.cpp000066400000000000000000000134261413061077700252120ustar00rootroot00000000000000#include "compositemaprenderer.hpp" #include #include #include #include #include #include namespace Terrain { CompositeMapRenderer::CompositeMapRenderer() : mTargetFrameRate(120) , mMinimumTimeAvailable(0.0025) { setSupportsDisplayList(false); setCullingActive(false); mFBO = new osg::FrameBufferObject; mUnrefQueue = new SceneUtil::UnrefQueue; getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } CompositeMapRenderer::~CompositeMapRenderer() { } void CompositeMapRenderer::setWorkQueue(SceneUtil::WorkQueue* workQueue) { mWorkQueue = workQueue; } 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); if (mWorkQueue) mUnrefQueue->flush(mWorkQueue.get()); 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; if (mWorkQueue) { mUnrefQueue->push(compositeMap.mDrawables[i]); } 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.47.0/components/terrain/compositemaprenderer.hpp000066400000000000000000000044421413061077700252150ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H #define OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H #include #include #include namespace osg { class FrameBufferObject; class RenderInfo; class Texture2D; } namespace SceneUtil { class UnrefQueue; class WorkQueue; } 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 a WorkQueue to delete compiled composite map layers in the background thread void setWorkQueue(SceneUtil::WorkQueue* workQueue); /// 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; osg::ref_ptr mUnrefQueue; osg::ref_ptr mWorkQueue; typedef std::set > CompileSet; mutable CompileSet mCompileSet; mutable CompileSet mImmediateCompileSet; mutable std::mutex mMutex; osg::ref_ptr mFBO; }; } #endif openmw-openmw-0.47.0/components/terrain/defs.hpp000066400000000000000000000010471413061077700217050ustar00rootroot00000000000000#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.47.0/components/terrain/material.cpp000066400000000000000000000223601413061077700225560ustar00rootroot00000000000000#include "material.hpp" #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 osg::Depth) { 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 osg::Depth) { mValue->setFunction(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); } }; } namespace Terrain { std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { std::vector > passes; unsigned int blendmapIndex = 0; unsigned int passIndex = 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); stateset->setRenderBinDetails(passIndex++, "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); } } int texunit = 0; if (useShaders) { stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); if (layerTileSize != 1.f) stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); if (!blendmaps.empty()) { ++texunit; osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(texunit, blendmap.get()); stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(new osg::Uniform("blendMap", texunit)); } if (it->mNormalMap) { ++texunit; stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); stateset->addUniform(new osg::Uniform("normalMap", texunit)); } 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"; 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, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); stateset->addUniform(new osg::Uniform("colorMode", 2)); } else { // Add the actual layer texture osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(texunit, tex.get()); if (layerTileSize != 1.f) stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); ++texunit; // Multiply by the alpha map if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(texunit, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); stateset->setTextureAttributeAndModes(texunit, TexEnvCombine::value(), osg::StateAttribute::ON); ++texunit; } } passes.push_back(stateset); } return passes; } } openmw-openmw-0.47.0/components/terrain/material.hpp000066400000000000000000000014561413061077700225660ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H #include #include "defs.hpp" namespace osg { class Texture2D; } namespace Shader { class ShaderManager; } namespace Terrain { struct TextureLayer { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional bool mParallax; bool mSpecular; }; std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); } #endif openmw-openmw-0.47.0/components/terrain/quadtreenode.cpp000066400000000000000000000072441413061077700234440ustar00rootroot00000000000000#include "quadtreenode.hpp" #include #include #include "defs.hpp" #include "viewdata.hpp" namespace Terrain { 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(); 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(); } } 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.47.0/components/terrain/quadtreenode.hpp000066400000000000000000000052211413061077700234420ustar00rootroot00000000000000#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; 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.47.0/components/terrain/quadtreeworld.cpp000066400000000000000000000461101413061077700236410ustar00rootroot00000000000000#include "quadtreeworld.hpp" #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" 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; } int Log2( unsigned int n ) { 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) : mFactor(factor) , mMinSize(minSize) , mViewDistance(viewDistance) , mActiveGrid(grid) { } 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 (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; 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; } int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper; } private: float mFactor; float mMinSize; float mViewDistance; osg::Vec4i mActiveGrid; }; 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; }; 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) : 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) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); } QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize) : TerrainGrid(parent, storage, nodeMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) , mVertexLodMod(0) , mViewDistance(std::numeric_limits::max()) , mMinSize(chunkSize) { } QuadTreeWorld::~QuadTreeWorld() { } /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) { int lod = Log2(int(node->getSize())); if (vertexLodMod > 0) { lod = std::max(0, lod-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); } lod += std::abs(vertexLodMod); } return lod; } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly unsigned int getLodFlags(QuadTreeNode* node, int ourLod, 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(); int lod = 0; if (neighbour) lod = getVertexLod(neighbour, vertexLodMod); if (lod <= ourLod) // 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 |= static_cast(lod - ourLod) << (4*i); } } return lodFlags; } void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; int ourLod = getVertexLod(entry.mNode, vertexLodMod); if (vd->hasChanged()) { // have to recompute the lodFlags in case a neighbour has changed LOD. unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vertexLodMod, 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 : chunkManagers) { osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, 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) { ViewData::Entry& 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) { if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) { if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos) { SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast(&nv); clsb->apply(*this); } else nv.apply(*mRootNode); } return; } osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false); entry.mRenderingNode->accept(nv); } if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); 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); } if (mRootNode) mRootNode->setNodeMask(enabled ? ~0 : 0); } View* QuadTreeWorld::createView() { return mViewDataMap->createIndependentView(); } void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, std::atomic &progress, int& progressTotal) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (!progressTotal) for (unsigned int i=0; igetNumEntries(); ++i) progressTotal += vd->getEntry(i).mNode->getSize(); const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true); progress += entry.mNode->getSize(); } vd->markUnchanged(); } bool QuadTreeWorld::storeView(const View* view, double referenceTime) { return mViewDataMap->storeView(static_cast(view), referenceTime); } 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()); } void QuadTreeWorld::rebuildViews() { mViewDataMap->rebuildViews(); } } openmw-openmw-0.47.0/components/terrain/quadtreeworld.hpp000066400000000000000000000050001413061077700236370ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H #define COMPONENTS_TERRAIN_QUADTREEWORLD_H #include "world.hpp" #include "terraingrid.hpp" #include namespace osg { class NodeVisitor; } namespace Terrain { class RootNode; class ViewDataMap; /// @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); QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize); ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); void enable(bool enabled) override; void setViewDistance(float distance) override { mViewDistance = distance; } 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, std::atomic& progress, int& progressRange) override; bool storeView(const View* view, double referenceTime) 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 addChunkManager(ChunkManager*); private: void ensureQuadTreeBuilt(); osg::ref_ptr mRootNode; osg::ref_ptr mViewDataMap; std::vector mChunkManagers; std::mutex mQuadTreeMutex; bool mQuadTreeBuilt; float mLodFactor; int mVertexLodMod; float mViewDistance; float mMinSize; }; } #endif openmw-openmw-0.47.0/components/terrain/storage.hpp000066400000000000000000000100331413061077700224230ustar00rootroot00000000000000#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.47.0/components/terrain/terraindrawable.cpp000066400000000000000000000112441413061077700241250ustar00rootroot00000000000000#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->setImmediate(mCompositeMap); mCompositeMap = 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.47.0/components/terrain/terraindrawable.hpp000066400000000000000000000045001413061077700241270ustar00rootroot00000000000000#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); 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; } 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.47.0/components/terrain/terraingrid.cpp000066400000000000000000000072641413061077700233000ustar00rootroot00000000000000#include "terraingrid.hpp" #include #include #include #include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" #include "storage.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.47.0/components/terrain/terraingrid.hpp000066400000000000000000000024631413061077700233010ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H #include #include #include "world.hpp" namespace Terrain { /// @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.47.0/components/terrain/texturemanager.cpp000066400000000000000000000034241413061077700240130ustar00rootroot00000000000000#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.47.0/components/terrain/texturemanager.hpp000066400000000000000000000012751413061077700240220ustar00rootroot00000000000000#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.47.0/components/terrain/viewdata.cpp000066400000000000000000000126321413061077700225650ustar00rootroot00000000000000#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); Entry& entry = mEntries[index]; if (entry.set(node)) mChanged = true; } unsigned int ViewData::getNumEntries() const { return mNumEntries; } ViewData::Entry &ViewData::getEntry(unsigned int i) { return mEntries[i]; } bool ViewData::hasChanged() const { return mChanged; } bool ViewData::hasViewPoint() const { return mHasViewPoint; } void ViewData::setViewPoint(const osg::Vec3f &viewPoint) { mViewPoint = viewPoint; mHasViewPoint = true; } const osg::Vec3f& ViewData::getViewPoint() const { return mViewPoint; } 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) { vd->setViewPoint(viewPoint); needsUpdate = true; } } if (!vd->suitableToUse(activeGrid)) { vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); needsUpdate = true; } return vd; } bool ViewDataMap::storeView(const ViewData* view, double referenceTime) { if (view->getWorldUpdateRevision() < mWorldUpdateRevision) return false; ViewData* store = createOrReuseView(); store->copyFrom(*view); store->setLastUsageTimeStamp(referenceTime); return true; } 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.47.0/components/terrain/viewdata.hpp000066400000000000000000000064641413061077700226000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H #define OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H #include #include #include #include "world.hpp" namespace Terrain { class QuadTreeNode; 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); struct Entry { Entry(); bool set(QuadTreeNode* node); QuadTreeNode* mNode; unsigned int mLodFlags; osg::ref_ptr mRenderingNode; }; unsigned int getNumEntries() const; Entry& getEntry(unsigned int i); double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; } void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; } /// @return Have any nodes changed since the last frame bool hasChanged() const; void markUnchanged() { mChanged = false; } bool hasViewPoint() const; void setViewPoint(const osg::Vec3f& viewPoint); const osg::Vec3f& getViewPoint() const; 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(); bool storeView(const ViewData* view, double referenceTime); 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.47.0/components/terrain/world.cpp000066400000000000000000000102711413061077700221050ustar00rootroot00000000000000#include "world.hpp" #include #include #include #include "storage.hpp" #include "texturemanager.hpp" #include "chunkmanager.hpp" #include "compositemaprenderer.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.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); mChunkManager->setNodeMask(nodeMask); mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); 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::setWorkQueue(SceneUtil::WorkQueue* workQueue) { mCompositeMapRenderer->setWorkQueue(workQueue); } 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.47.0/components/terrain/world.hpp000066400000000000000000000130411413061077700221100ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H #include #include #include #include #include #include #include #include #include "defs.hpp" #include "cellborder.hpp" namespace osg { class Group; class Stats; class Node; class Object; } namespace Resource { class ResourceSystem; } namespace SceneUtil { class WorkQueue; } namespace Terrain { class Storage; class TextureManager; class ChunkManager; class CompositeMapRenderer; class HeightCullCallback : public osg::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) override { if (mLowZ <= mHighZ) traverse(node, nv); } private: float mLowZ{-std::numeric_limits::max()}; float mHighZ{std::numeric_limits::max()}; unsigned int mMask{~0u}; }; /** * @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; }; /** * @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(); /// Set a WorkQueue to delete objects in the background thread. void setWorkQueue(SceneUtil::WorkQueue* workQueue); /// 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, std::atomic& progress, int& progressRange) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. virtual bool storeView(const View* view, double referenceTime) {return true;} 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.47.0/components/to_utf8/000077500000000000000000000000001413061077700201755ustar00rootroot00000000000000openmw-openmw-0.47.0/components/to_utf8/.gitignore000066400000000000000000000000121413061077700221560ustar00rootroot00000000000000gen_iconv openmw-openmw-0.47.0/components/to_utf8/Makefile000066400000000000000000000002001413061077700216250ustar00rootroot00000000000000tables_gen.hpp: gen_iconv ./gen_iconv > tables_gen.hpp gen_iconv: gen_iconv.cpp g++ -Wall $^ -o $@ clean: rm -f ./gen_iconvopenmw-openmw-0.47.0/components/to_utf8/gen_iconv.cpp000066400000000000000000000061361413061077700226560ustar00rootroot00000000000000// 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 << "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.47.0/components/to_utf8/tables_gen.hpp000066400000000000000000000637561413061077700230320ustar00rootroot00000000000000#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. 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 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 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 }; 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.47.0/components/to_utf8/tests/000077500000000000000000000000001413061077700213375ustar00rootroot00000000000000openmw-openmw-0.47.0/components/to_utf8/tests/.gitignore000066400000000000000000000000071413061077700233240ustar00rootroot00000000000000*_test openmw-openmw-0.47.0/components/to_utf8/tests/output/000077500000000000000000000000001413061077700226775ustar00rootroot00000000000000openmw-openmw-0.47.0/components/to_utf8/tests/output/to_utf8_test.out000066400000000000000000000012701413061077700260570ustar00rootroot00000000000000original: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? converted: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. openmw-openmw-0.47.0/components/to_utf8/tests/test.sh000077500000000000000000000004441413061077700226570ustar00rootroot00000000000000#!/bin/bash make || exit mkdir -p output PROGS=*_test for a in $PROGS; do if [ -f "output/$a.out" ]; then echo "Running $a:" ./$a | diff output/$a.out - else echo "Creating $a.out" ./$a > "output/$a.out" git add "output/$a.out" fi done openmw-openmw-0.47.0/components/to_utf8/tests/test_data/000077500000000000000000000000001413061077700233075ustar00rootroot00000000000000openmw-openmw-0.47.0/components/to_utf8/tests/test_data/french-utf8.txt000066400000000000000000000001531413061077700262000ustar00rootroot00000000000000Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger.openmw-openmw-0.47.0/components/to_utf8/tests/test_data/french-win1252.txt000066400000000000000000000001501413061077700264160ustar00rootroot00000000000000Vous lui donnez le gteau sans protester avant daller chercher tous vos amis et de revenir vous venger.openmw-openmw-0.47.0/components/to_utf8/tests/test_data/russian-utf8.txt000066400000000000000000000003311413061077700264150ustar00rootroot00000000000000Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам?openmw-openmw-0.47.0/components/to_utf8/tests/test_data/russian-win1251.txt000066400000000000000000000001701413061077700266360ustar00rootroot00000000000000 , , ?openmw-openmw-0.47.0/components/to_utf8/tests/to_utf8_test.cpp000066400000000000000000000033151413061077700244740ustar00rootroot00000000000000#include #include #include #include #include "../to_utf8.hpp" std::string getFirstLine(const std::string &filename); void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, const std::string &utf8File); /// Test character encoding conversion to and from UTF-8 void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, const std::string &utf8File) { // get some test data std::string legacyEncLine = getFirstLine(legacyEncFile); std::string utf8Line = getFirstLine(utf8File); // create an encoder for specified character encoding ToUTF8::Utf8Encoder encoder (encoding); // convert text to UTF-8 std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine); std::cout << "original: " << utf8Line << std::endl; std::cout << "converted: " << convertedUtf8Line << std::endl; // check correctness assert(convertedUtf8Line == utf8Line); // convert UTF-8 text to legacy encoding std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line); // check correctness assert(convertedLegacyEncLine == legacyEncLine); } std::string getFirstLine(const std::string &filename) { std::string line; std::ifstream text (filename.c_str()); if (!text.is_open()) { throw std::runtime_error("Unable to open file " + filename); } std::getline(text, line); text.close(); return line; } int main() { testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt"); testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt"); return 0; } openmw-openmw-0.47.0/components/to_utf8/to_utf8.cpp000066400000000000000000000242731413061077700223010ustar00rootroot00000000000000#include "to_utf8.hpp" #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; Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): mOutput(50*1024) { switch (sourceEncoding) { case ToUTF8::WINDOWS_1252: { translationArray = ToUTF8::windows_1252; break; } case ToUTF8::WINDOWS_1250: { translationArray = ToUTF8::windows_1250; break; } case ToUTF8::WINDOWS_1251: { translationArray = ToUTF8::windows_1251; break; } case ToUTF8::CP437: { translationArray = ToUTF8::cp437; break; } default: { assert(0); } } } std::string Utf8Encoder::getUtf8(const char* input, size_t size) { // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) assert(input[size] == 0); // 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. bool ascii; size_t outlen = getLength(input, ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) return std::string(input, outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate while (*input) copyFromArray(*(input++), out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); // And make extra sure the output is null terminated assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); // Return a string return std::string(&mOutput[0], outlen); } std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) { // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) assert(input[size] == 0); // 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. bool ascii; size_t outlen = getLength2(input, ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) return std::string(input, outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate while(*input) copyFromArray2(input, out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); // And make extra sure the output is null terminated assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); // Return a string return std::string(&mOutput[0], outlen); } // Make sure the output vector is large enough for 'size' bytes, // including a terminating zero after it. void Utf8Encoder::resize(size_t size) { if (mOutput.size() <= size) // Add some extra padding to reduce the chance of having to resize // again later. mOutput.resize(3*size); // And make sure the string is zero terminated mOutput[size] = 0; } /** 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. */ size_t Utf8Encoder::getLength(const char* input, bool &ascii) { ascii = true; size_t len = 0; const char* ptr = input; unsigned char inp = *ptr; // Do away with the ascii part of the string first (this is almost // always the entire string.) while (inp && inp < 128) inp = *(++ptr); len += (ptr-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 (inp) { ascii = false; while (inp) { // Find the translated length of this character in the // lookup table. len += translationArray[inp*6]; inp = *(++ptr); } } return len; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) { // Optimize for ASCII values if (ch < 128) { *(out++) = ch; return; } const signed char *in = translationArray + ch*6; int len = *(in++); for (int i=0; i #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. }; FromType calculateEncoding(const std::string& encodingName); std::string encodingUsingMessage(const std::string& encodingName); // class class Utf8Encoder { public: Utf8Encoder(FromType sourceEncoding); // Convert to UTF8 from the previously given code page. std::string getUtf8(const char *input, size_t size); inline std::string getUtf8(const std::string &str) { return getUtf8(str.c_str(), str.size()); } std::string getLegacyEnc(const char *input, size_t size); inline std::string getLegacyEnc(const std::string &str) { return getLegacyEnc(str.c_str(), str.size()); } private: void resize(size_t size); size_t getLength(const char* input, bool &ascii); void copyFromArray(unsigned char chp, char* &out); size_t getLength2(const char* input, bool &ascii); void copyFromArray2(const char*& chp, char* &out); std::vector mOutput; signed char* translationArray; }; } #endif openmw-openmw-0.47.0/components/translation/000077500000000000000000000000001413061077700211435ustar00rootroot00000000000000openmw-openmw-0.47.0/components/translation/translation.cpp000066400000000000000000000073441413061077700242150ustar00rootroot00000000000000#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()) { line = mEncoder->getUtf8(line); size_t tab_pos = line.find('\t'); if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) { std::string key = line.substr(0, tab_pos); std::string value = line.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.insert(std::make_pair(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.47.0/components/translation/translation.hpp000066400000000000000000000023711413061077700242150ustar00rootroot00000000000000#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.47.0/components/version/000077500000000000000000000000001413061077700202725ustar00rootroot00000000000000openmw-openmw-0.47.0/components/version/version.cpp000066400000000000000000000015101413061077700224600ustar00rootroot00000000000000#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.47.0/components/version/version.hpp000066400000000000000000000010431413061077700224660ustar00rootroot00000000000000#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.47.0/components/vfs/000077500000000000000000000000001413061077700174035ustar00rootroot00000000000000openmw-openmw-0.47.0/components/vfs/archive.hpp000066400000000000000000000015131413061077700215350ustar00rootroot00000000000000#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; }; 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.47.0/components/vfs/bsaarchive.cpp000066400000000000000000000035101413061077700222150ustar00rootroot00000000000000#include "bsaarchive.hpp" #include #include namespace VFS { BsaArchive::BsaArchive(const std::string &filename) { Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(filename); if (bsaVersion == Bsa::BSAVER_COMPRESSED) { mFile = std::make_unique(Bsa::CompressedBSAFile()); } else { mFile = std::make_unique(Bsa::BSAFile()); } 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() { } 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); } } openmw-openmw-0.47.0/components/vfs/bsaarchive.hpp000066400000000000000000000016321413061077700222250ustar00rootroot00000000000000#ifndef VFS_BSAARCHIVE_HPP_ #define VFS_BSAARCHIVE_HPP_ #include "archive.hpp" #include namespace VFS { class BsaArchiveFile : public File { public: BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::BSAFile* bsa); Files::IStreamPtr open() override; const Bsa::BSAFile::FileStruct* mInfo; Bsa::BSAFile* mFile; }; class BsaArchive : public Archive { public: BsaArchive(const std::string& filename); 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; private: std::unique_ptr mFile; std::vector mResources; }; } #endif openmw-openmw-0.47.0/components/vfs/filesystemarchive.cpp000066400000000000000000000042551413061077700236430ustar00rootroot00000000000000#include "filesystemarchive.hpp" #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); if (!mIndex.insert (std::make_pair (searchable, file)).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."; } mBuiltIndex = true; } 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.c_str()); } } openmw-openmw-0.47.0/components/vfs/filesystemarchive.hpp000066400000000000000000000016301413061077700236420ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #define OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #include "archive.hpp" namespace VFS { class FileSystemArchiveFile : public File { public: FileSystemArchiveFile(const std::string& path); Files::IStreamPtr open() override; 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.47.0/components/vfs/manager.cpp000066400000000000000000000052521413061077700215250ustar00rootroot00000000000000#include "manager.hpp" #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() { reset(); } void Manager::reset() { mIndex.clear(); for (std::vector::iterator it = mArchives.begin(); it != mArchives.end(); ++it) delete *it; mArchives.clear(); } void Manager::addArchive(Archive *archive) { mArchives.push_back(archive); } void Manager::buildIndex() { mIndex.clear(); for (std::vector::const_iterator it = mArchives.begin(); it != mArchives.end(); ++it) (*it)->listResources(mIndex, mStrict ? &strict_normalize_char : &nonstrict_normalize_char); } Files::IStreamPtr Manager::get(const std::string &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(const std::string &name) const { std::string normalized = name; normalize_path(normalized, mStrict); return mIndex.find(normalized) != mIndex.end(); } const std::map& Manager::getIndex() const { return mIndex; } void Manager::normalizeFilename(std::string &name) const { normalize_path(name, mStrict); } std::string Manager::getArchive(const std::string& 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 {}; } } openmw-openmw-0.47.0/components/vfs/manager.hpp000066400000000000000000000053071413061077700215330ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCEMANAGER_H #define OPENMW_COMPONENTS_RESOURCEMANAGER_H #include #include #include namespace VFS { class Archive; class File; /// @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 { 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. /// @note Takes ownership of the given pointer. void addArchive(Archive* 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(const std::string& name) const; /// Get a complete list of files from all archives /// @note May be called from any thread once the index has been built. const std::map& getIndex() 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. void normalizeFilename(std::string& 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(const std::string& 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(const std::string& name) const; private: bool mStrict; std::vector mArchives; std::map mIndex; }; } #endif openmw-openmw-0.47.0/components/vfs/registerarchives.cpp000066400000000000000000000035361413061077700234670ustar00rootroot00000000000000#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; vfs->addArchive(new BsaArchive(archivePath)); } else { std::stringstream message; message << "Archive '" << *archive << "' not found"; throw std::runtime_error(message.str()); } } 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(new FileSystemArchive(iter->string())); } else Log(Debug::Info) << "Ignoring duplicate data directory " << iter->string(); } } vfs->buildIndex(); } } openmw-openmw-0.47.0/components/vfs/registerarchives.hpp000066400000000000000000000006721413061077700234720ustar00rootroot00000000000000#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.47.0/components/widgets/000077500000000000000000000000001413061077700202535ustar00rootroot00000000000000openmw-openmw-0.47.0/components/widgets/box.cpp000066400000000000000000000354021413061077700215530ustar00rootroot00000000000000#include "box.hpp" #include namespace Gui { 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.47.0/components/widgets/box.hpp000066400000000000000000000102061413061077700215530ustar00rootroot00000000000000#ifndef OPENMW_WIDGETS_BOX_H #define OPENMW_WIDGETS_BOX_H #include #include #include #include #include #include "fontwrapper.hpp" namespace Gui { 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.47.0/components/widgets/fontwrapper.hpp000066400000000000000000000025371413061077700233420ustar00rootroot00000000000000#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: static int clamp(const int& value, const int& lowBound, const int& highBound) { return std::min(std::max(lowBound, value), highBound); } 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(clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)); return fontSize; } }; } #endif openmw-openmw-0.47.0/components/widgets/imagebutton.cpp000066400000000000000000000104341413061077700232770ustar00rootroot00000000000000#include "imagebutton.hpp" #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) { int scale = 1.f; MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(textureName); if (texture && getHeight() != 0) scale = texture->getHeight() / getHeight(); setImageTile(MyGUI::IntSize(mTextureRect.width * scale, mTextureRect.height * scale)); MyGUI::IntCoord scaledSize(mTextureRect.left * scale, mTextureRect.top * scale, mTextureRect.width * scale, mTextureRect.height * scale); 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.47.0/components/widgets/imagebutton.hpp000066400000000000000000000031151413061077700233020ustar00rootroot00000000000000#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.47.0/components/widgets/list.cpp000066400000000000000000000124361413061077700217400ustar00rootroot00000000000000#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.47.0/components/widgets/list.hpp000066400000000000000000000042721413061077700217440ustar00rootroot00000000000000#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.47.0/components/widgets/numericeditbox.cpp000066400000000000000000000046141413061077700240050ustar00rootroot00000000000000#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::min(mMaxValue, std::max(mValue, mMinValue)); 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.47.0/components/widgets/numericeditbox.hpp000066400000000000000000000023311413061077700240040ustar00rootroot00000000000000#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.47.0/components/widgets/sharedstatebutton.cpp000066400000000000000000000064721413061077700245330ustar00rootroot00000000000000#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(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.47.0/components/widgets/sharedstatebutton.hpp000066400000000000000000000026661413061077700245410ustar00rootroot00000000000000#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(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.47.0/components/widgets/tags.cpp000066400000000000000000000035711413061077700217230ustar00rootroot00000000000000#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.47.0/components/widgets/widgets.cpp000066400000000000000000000027701413061077700224330ustar00rootroot00000000000000#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"); } } openmw-openmw-0.47.0/components/widgets/widgets.hpp000066400000000000000000000003551413061077700224350ustar00rootroot00000000000000#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.47.0/components/widgets/windowcaption.cpp000066400000000000000000000026701413061077700236510ustar00rootroot00000000000000#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.47.0/components/widgets/windowcaption.hpp000066400000000000000000000014321413061077700236510ustar00rootroot00000000000000#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.47.0/docs/000077500000000000000000000000001413061077700153505ustar00rootroot00000000000000openmw-openmw-0.47.0/docs/Doxyfile.cmake000066400000000000000000003077241413061077700201520ustar00rootroot00000000000000# 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 # , /