diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..9f5442ae4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,42 @@ +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc + +cache: + key: apt-cache + paths: + - apt-cache/ + +before_script: + - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR + - apt-get update -yq + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev +# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb + +build: + stage: build + script: + - cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi + - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ + - make -j$cores_to_use + - DESTDIR=artifacts make install + artifacts: + paths: + - build/artifacts/ + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + cache: + paths: + - "*.o" + +# TODO: run tests using the binary built before +#test: +# stage: test +# script: +# - ls diff --git a/AUTHORS.md b/AUTHORS.md index 155d017f3..b13953824 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ Programmers Adam Hogan (aurix) Aesylwinn aegis + AHSauge Aleksandar Jovanov Alex Haddad (rainChu) Alex McKibben @@ -36,12 +37,15 @@ Programmers Britt Mathis (galdor557) Capostrophic cc9cii + Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 + DanielVukelich darkf + David Cernat (davidcernat) devnexen Dieho Dmitry Shkurskiy (endorph) @@ -59,6 +63,7 @@ Programmers Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) + Florian Weber (Florianjw) Gašper Sedej gugus/gus Hallfaer Tuilinn @@ -155,6 +160,8 @@ Programmers terrorfisch thegriglat Thomas Luppi (Digmaster) + tri4ng1e + unelsson Will Herrmann (Thunderforge) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) @@ -224,7 +231,7 @@ Artwork Necrod - OpenMW Logo Mickey Lyle (raevol) - Wordpress Theme - Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons + Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel, Lamoot - OpenMW Editor Icons Inactive Contributors --------------------- diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index ee48c1f68..38b304bf9 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1,5 +1,22 @@ #!/bin/bash +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; } + +if [ $MISSINGTOOLS -ne 0 ]; then + exit 1 +fi + +WORKINGDIR="$(pwd)" +case "$WORKINGDIR" in + *[[:space:]]*) + echo "Error: Working directory contains spaces." + exit 1 + ;; +esac + set -euo pipefail APPVEYOR=${APPVEYOR:-} @@ -216,6 +233,8 @@ case $VS_VERSION in 15|15.0|2017 ) GENERATOR="Visual Studio 15 2017" TOOLSET="vc140" + TOOLSET_REAL="vc141" + MSVC_REAL_VER="15" MSVC_VER="14" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2017" @@ -224,6 +243,8 @@ case $VS_VERSION in 14|14.0|2015 ) GENERATOR="Visual Studio 14 2015" TOOLSET="vc140" + TOOLSET_REAL="vc140" + MSVC_REAL_VER="14" MSVC_VER="14" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2015" @@ -232,6 +253,8 @@ case $VS_VERSION in 12|12.0|2013 ) GENERATOR="Visual Studio 12 2013" TOOLSET="vc120" + TOOLSET_REAL="vc120" + MSVC_REAL_VER="12" MSVC_VER="12" MSVC_YEAR="2013" MSVC_DISPLAY_YEAR="2013" @@ -303,25 +326,25 @@ if [ -z $SKIP_DOWNLOAD ]; then # Boost if [ -z $APPVEYOR ]; then download "Boost 1.61.0" \ - "http://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \ + "https://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \ "boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" fi # Bullet download "Bullet 2.86" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "https://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \ "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" # FFmpeg download "FFmpeg 3.2.4" \ - "http://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.2.4-win${BITS}-shared.zip" \ + "https://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.2.4-win${BITS}-shared.zip" \ "ffmpeg-3.2.4-win${BITS}.zip" \ - "http://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.2.4-win${BITS}-dev.zip" \ + "https://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.2.4-win${BITS}-dev.zip" \ "ffmpeg-3.2.4-dev-win${BITS}.zip" # MyGUI download "MyGUI 3.2.2" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "https://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \ "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" # OpenAL @@ -331,7 +354,7 @@ if [ -z $SKIP_DOWNLOAD ]; then # OSG download "OpenSceneGraph 3.4.1-scrawl" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "https://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" # Qt @@ -343,9 +366,9 @@ if [ -z $SKIP_DOWNLOAD ]; then fi download "Qt 5.7.2" \ - "http://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ + "https://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ "qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \ - "http://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ + "https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ "qt-5-install.qs" fi @@ -385,7 +408,7 @@ else if [ $MSVC_VER -eq 12 ]; then printf "Boost 1.58.0 AppVeyor... " else - printf "Boost 1.60.0 AppVeyor... " + printf "Boost 1.67.0 AppVeyor... " fi fi { @@ -408,14 +431,20 @@ fi echo Done. else # Appveyor unstable has all the boost we need already - if [ $MSVC_VER -eq 12 ]; then + if [ $MSVC_REAL_VER -eq 12 ]; then BOOST_SDK="c:/Libraries/boost_1_58_0" else - BOOST_SDK="c:/Libraries/boost_1_60_0" + BOOST_SDK="c:/Libraries/boost_1_67_0" fi + if [ $MSVC_REAL_VER -eq 15 ]; then + LIB_SUFFIX="1" + else + LIB_SUFFIX="0" + fi + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0" - add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" + -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}" + add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}" echo Done. fi @@ -568,7 +597,7 @@ echo if [ -z $APPVEYOR ]; then printf "Qt 5.7.0... " else - printf "Qt 5.7 AppVeyor... " + printf "Qt 5.10 AppVeyor... " fi { if [ $BITS -eq 64 ]; then @@ -613,18 +642,28 @@ fi SUFFIX="" fi - add_runtime_dlls "$(pwd)/bin/lib"{EGL,GLESv2}${SUFFIX}.dll \ - "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll + add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll" echo Done. else - QT_SDK="C:/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}" + QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}" add_cmake_opts -DDESIRED_QT_VERSION=5 \ -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d" + else + SUFFIX="" + fi + + DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + + add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll + add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll" + echo Done. fi } @@ -699,7 +738,7 @@ if [ ! -z $CI ]; then fi # NOTE: Disable this when/if we want to run test cases -if [ -z $CI ]; then +#if [ -z $CI ]; then echo "- Copying Runtime DLLs..." mkdir -p $BUILD_CONFIG for DLL in $RUNTIME_DLLS; do @@ -731,7 +770,7 @@ if [ -z $CI ]; then cp "$DLL" "${BUILD_CONFIG}/platforms" done echo -fi +#fi if [ -z $VERBOSE ]; then printf -- "- Configuring... " diff --git a/CI/deploy.osx.sh b/CI/deploy.osx.sh index 53bfa18b5..5fa2b70a3 100755 --- a/CI/deploy.osx.sh +++ b/CI/deploy.osx.sh @@ -6,4 +6,6 @@ DATE=`date +'%d%m%Y'` SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}` TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg" -curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}" +if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then + curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}" +fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 2923612fb..15ca3efb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 43) +set(OPENMW_VERSION_MINOR 44) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -135,6 +135,8 @@ if (WIN32) endif() # Dependencies +find_package(OpenGL REQUIRED) + if (USE_QT) message(STATUS "Using Qt${DESIRED_QT_VERSION}") @@ -182,12 +184,6 @@ if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind insta set(OPENMW_USE_UNSHIELD TRUE) endif() -option(OPENGL_ES "enable opengl es support" FALSE ) - -if (OPENGL_ES) - add_definitions(-DOPENGL_ES) -endif(OPENGL_ES) - # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) @@ -505,8 +501,8 @@ if(WIN32) 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 "http:\\\\\\\\www.openmw.org") - SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") + 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") @@ -549,11 +545,6 @@ endif() # Components add_subdirectory (components) -# Plugins -#if (BUILD_MYGUI_PLUGIN) -# add_subdirectory(plugins/mygui_resource_plugin) -#endif() - # Apps and tools if (BUILD_OPENMW) add_subdirectory( apps/openmw ) @@ -714,8 +705,7 @@ if (WIN32) endif() if (BUILD_OPENMW) - # Very specific issue this, only needed on 32-bit VS2015 during unity builds. - if (MSVC_VERSION GREATER 1800 AND CMAKE_SIZEOF_VOID_P EQUAL 4 AND OPENMW_UNITY_BUILD) + 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}") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c82d1dfd..4805bff3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,15 +3,14 @@ How to contribute to OpenMW Not sure what to do with all your free time? Pick out a task from here: -http://bugs.openmw.org/ +https://bugs.openmw.org/ 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. -- Larger Features should have a discussion before you start implementing. -- In many cases, it's best to have a discussion about possible solutions before you jump into coding. +* 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! @@ -20,20 +19,21 @@ There are various [Tools](https://wiki.openmw.org/index.php?title=Tools) to faci Pull Request Guidelines ======================= -Thought of a change? Great! To facilitate the review process, your pull request description should include the following (if applicable): +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 +* A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://bugs.openmw.org/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: -* Separate your work into multiple pull requests whenever possible. 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. +* 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" ================================= @@ -47,7 +47,7 @@ Unfortunately, the definition of what is a "bug" is not so clear. Consider that * 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 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. +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: @@ -62,10 +62,54 @@ We get it, you have waited so long for feature XYZ to be available in Morrowind 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. +* 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/) + diff --git a/LICENSE b/LICENSE index 9cecc1d46..9d742475f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + 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. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. 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 . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ 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 -. +. diff --git a/README.md b/README.md index 0b5f63f93..9af9ef976 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ OpenMW ====== -[![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/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![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 a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +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 Morrowind's TES Construction Set. +OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. -* Version: 0.43.0 +* Version: 0.44.0 * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) -* Website: http://www.openmw.org +* Website: https://www.openmw.org * IRC: #openmw on irc.freenode.net Font Licenses: @@ -30,8 +30,8 @@ Getting Started * [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](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! -* [Known issues](http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker) +* [Report a bug](https://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! +* [Known issues](https://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker) The data path ------------- diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 3123308ef..089a25c93 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -694,7 +694,7 @@ void Record::print() // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? ESM::Dialogue::InfoContainer::iterator iit; - for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); iit++) + for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); ++iit) std::cout << "INFO!" << iit->mId << std::endl; } @@ -1040,45 +1040,47 @@ void Record::print() if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; - std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; - std::cout << " Disposition: " << (int)mData.mNpdt12.mDisposition << std::endl; - std::cout << " Rank: " << (int)mData.mNpdt12.mRank << std::endl; - std::cout << " Unknown1: " - << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl; - std::cout << " Unknown2: " - << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl; - std::cout << " Unknown3: " - << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl; - std::cout << " Gold: " << mData.mNpdt12.mGold << std::endl; + 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; + //Why do we want to print these fields? They are padding in the struct and contain + // nothing of real value. Now we don't deal with NPDTstruct12 in runtime either... + //std::cout << " Unknown1: " + // << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl; + //std::cout << " Unknown2: " + // << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl; + //std::cout << " Unknown3: " + // << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl; + std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; } else { - std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl; - std::cout << " Reputation: " << (int)mData.mNpdt52.mReputation << std::endl; - std::cout << " Disposition: " << (int)mData.mNpdt52.mDisposition << std::endl; - std::cout << " Rank: " << (int)mData.mNpdt52.mRank << std::endl; - std::cout << " FactionID: " << (int)mData.mNpdt52.mFactionID << std::endl; + 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 << " FactionID: " << (int)mData.mNpdt.mFactionID << std::endl; std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << (int)mData.mNpdt52.mStrength << std::endl; - std::cout << " Intelligence: " << (int)mData.mNpdt52.mIntelligence << std::endl; - std::cout << " Willpower: " << (int)mData.mNpdt52.mWillpower << std::endl; - std::cout << " Agility: " << (int)mData.mNpdt52.mAgility << std::endl; - std::cout << " Speed: " << (int)mData.mNpdt52.mSpeed << std::endl; - std::cout << " Endurance: " << (int)mData.mNpdt52.mEndurance << std::endl; - std::cout << " Personality: " << (int)mData.mNpdt52.mPersonality << std::endl; - std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << 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.mNpdt52.mSkills[i]) << std::endl; + << (int)(mData.mNpdt.mSkills[i]) << std::endl; - std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl; - std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl; - std::cout << " Fatigue: " << mData.mNpdt52.mFatigue << std::endl; - std::cout << " Unknown: " << (int)mData.mNpdt52.mUnknown << std::endl; - std::cout << " Gold: " << mData.mNpdt52.mGold << 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 << " Unknown: " << (int)mData.mNpdt.mUnknown << std::endl; + std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; } std::vector::iterator cit; @@ -1123,7 +1125,7 @@ void Record::print() int i = 0; ESM::Pathgrid::PointList::iterator pit; - for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); pit++) + for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); ++pit) { std::cout << " Point[" << i << "]:" << std::endl; std::cout << " Coordinates: (" << pit->mX << "," @@ -1135,7 +1137,7 @@ void Record::print() } i = 0; ESM::Pathgrid::EdgeList::iterator eit; - for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); eit++) + for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); ++eit) { std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl; if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 621b85709..1772e0e2d 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -122,7 +122,7 @@ public: } else { - mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; + 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, diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 73b15baae..4538d4e63 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -377,7 +377,7 @@ namespace ESSImport profile.mPlayerClassName = context.mCustomPlayerClassName; else profile.mPlayerClassId = context.mPlayerBase.mClass; - profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel; + profile.mPlayerLevel = context.mPlayerBase.mNpdt.mLevel; profile.mPlayerName = header.mGameData.mPlayerName.toString(); writeScreenshot(header, profile); diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index aec8c2533..ec2e963d1 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -8,6 +8,7 @@ set(LAUNCHER settingspage.cpp advancedpage.cpp + utils/cellnameloader.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,6 +25,7 @@ set(LAUNCHER_HEADER settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp @@ -39,6 +41,7 @@ set(LAUNCHER_HEADER_MOC settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/textinputdialog.hpp utils/profilescombobox.hpp utils/lineedit.hpp @@ -106,7 +109,7 @@ if (DESIRED_QT_VERSION MATCHES 4) target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY}) endif(WIN32) else() - qt5_use_modules(openmw-launcher Widgets Core) + target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) endif() if (BUILD_WITH_CODE_COVERAGE) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 3ba378599..2b2d7b448 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -1,10 +1,18 @@ #include "advancedpage.hpp" -#include - -Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) +#include +#include +#include +#include +#include +#include + +Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, + Config::GameSettings &gameSettings, + Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) + , mGameSettings(gameSettings) , mEngineSettings(engineSettings) { setObjectName ("AdvancedPage"); @@ -13,8 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings: loadSettings(); } +void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) { + // Set up an auto-completer for the "Start default character at" field + auto *completer = new QCompleter(cellNames); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + startDefaultCharacterAtField->setCompleter(completer); + +} + +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())); + +} + bool Launcher::AdvancedPage::loadSettings() { + // Testing + 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")); + // Game Settings loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); @@ -23,6 +77,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); // Expected values are (0, 1, 2, 3) int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); @@ -35,9 +90,11 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); - // Other Settings + // Saves Settings loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + // Other Settings QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) screenshotFormatComboBox->addItem(screenshotFormatString); @@ -51,6 +108,19 @@ void Launcher::AdvancedPage::saveSettings() // 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) + // Testing + 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); + // Game Settings saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); @@ -59,6 +129,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) @@ -69,9 +140,14 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); - // Other Settings + // Saves Settings saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); + int maximumQuicksaves = maximumQuicksavesComboBox->value(); + if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) { + mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); + } + // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) mEngineSettings.setString("screenshot format", "General", screenshotFormatString); @@ -86,4 +162,9 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str bool cValue = checkbox->checkState(); if (cValue != mEngineSettings.getBool(setting, group)) mEngineSettings.setBool(setting, group, cValue); +} + +void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) +{ + loadCellsForAutocomplete(cellNames); } \ No newline at end of file diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index a8361c98e..59de3d319 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -8,6 +8,7 @@ #include namespace Files { struct ConfigurationManager; } +namespace Config { class GameSettings; } namespace Launcher { @@ -16,15 +17,29 @@ namespace Launcher Q_OBJECT public: - AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); + AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Settings::Manager &engineSettings, QWidget *parent = 0); bool loadSettings(); void saveSettings(); + public slots: + void slotLoadedCellsChanged(QStringList cellNames); + + private slots: + void on_skipMenuCheckBox_stateChanged(int state); + void on_runScriptAfterStartupBrowseButton_clicked(); + private: Files::ConfigurationManager &mCfgMgr; + Config::GameSettings &mGameSettings; Settings::Manager &mEngineSettings; + /** + * 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); }; diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 0b0f8c75e..7b703a924 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,7 +7,10 @@ #include #include #include +#include +#include +#include #include #include @@ -16,6 +19,7 @@ #include #include +#include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" @@ -40,6 +44,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: 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() @@ -142,6 +153,17 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mGameSettings.setContentList(fileNames); } +QStringList Launcher::DataFilesPage::selectedFilePaths() +{ + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + QStringList filePaths; + foreach(const ContentSelectorModel::EsmFile *item, items) { + filePaths.append(item->filePath()); + } + return filePaths; +} + void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.removeContentList(profile); @@ -308,3 +330,31 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) 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; + QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); + std::sort(cellNamesList.begin(), cellNamesList.end()); + emit signalLoadedCellsChanged(cellNamesList); +} \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index d25d20fc9..2cbace38e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -7,6 +7,7 @@ #include #include +#include class QSortFilterProxyModel; class QAbstractItemModel; @@ -41,8 +42,15 @@ namespace Launcher 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); @@ -52,6 +60,7 @@ namespace Launcher void slotProfileChangedByUser(const QString &previous, const QString ¤t); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void slotAddonDataChanged (); void updateOkButton(const QString &text); @@ -72,7 +81,7 @@ namespace Launcher Config::LauncherSettings &mLauncherSettings; QString mPreviousProfile; - + QStringList previousSelectedFiles; QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); @@ -87,6 +96,7 @@ namespace Launcher void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); + void reloadCells(QStringList selectedFiles); class PathIterator { diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 1a210ccc5..27fa2d903 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages() mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mCfgMgr, mEngineSettings, this); + mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -139,6 +139,8 @@ void Launcher::MainDialog::createPages() 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); } diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp new file mode 100644 index 000000000..6d1ed2f49 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.cpp @@ -0,0 +1,48 @@ +#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); +} \ No newline at end of file diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp new file mode 100644 index 000000000..c58d09226 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_CELLNAMELOADER_H +#define OPENMW_CELLNAMELOADER_H + +#include +#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 diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 24646b844..6f5c2e2cd 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -653,12 +654,6 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } -std::string MwIniImporter::numberToString(int n) { - std::stringstream str; - str << n; - return str.str(); -} - MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { std::cout << "load ini file: " << filename << std::endl; @@ -800,7 +795,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con multistrmap::const_iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { archive = baseArchive; - archive.append(this->numberToString(i)); + archive.append(std::to_string(i)); it = ini.find(archive); if(it == ini.end()) { @@ -824,33 +819,105 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } } -void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { - std::vector > contentFiles; +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() == '"') + { + path.erase(path.begin()); + path.erase(path.end() - 1); + } + 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::string gameFile(""); std::time_t defaultTime = 0; + ToUTF8::Utf8Encoder encoder(mEncoding); + + std::vector dataPaths; + if (cfg.count("data")) + addPaths(dataPaths, cfg["data"]); - // assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini - const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); + 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++) { + for (int i=0; it != ini.end(); i++) + { gameFile = baseGameFile; - gameFile.append(this->numberToString(i)); + gameFile.append(std::to_string(i)); it = ini.find(gameFile); - if(it == ini.end()) { + if(it == ini.end()) break; - } - for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { + 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) { - boost::filesystem::path filepath(gameFilesDir); - filepath /= *entry; - contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry)); + 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.push_back({time, path}); + found = true; + break; + } + } + if (!found) + std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl; } } } @@ -858,11 +925,46 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co cfg.erase("content"); cfg.insert( std::make_pair("content", std::vector() ) ); - // this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. + // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); - for(std::vector >::const_iterator iter=contentFiles.begin(); iter!=contentFiles.end(); ++iter) { - cfg["content"].push_back(iter->second); + + 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) { @@ -901,9 +1003,5 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename std::cout << "content file: " << resolved << " timestamp = (" << writeTime << ") " << timeStrBuffer << std::endl; } - else - { - std::cout << "content file: " << filename << " not found" << std::endl; - } return writeTime; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index c73cc65b5..7b710a4a4 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -14,6 +14,7 @@ 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); @@ -22,14 +23,19 @@ class MwIniImporter { 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, + 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 std::string numberToString(int n); + 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); @@ -40,5 +46,4 @@ class MwIniImporter { ToUTF8::FromType mEncoding; }; - #endif diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 72393db40..572a58f26 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -33,12 +33,12 @@ bool hasExtension(std::string filename, std::string extensionToFind) } ///See if the file has the "nif" extension. -bool isNIF(std::string filename) +bool isNIF(const std::string & filename) { return hasExtension(filename,"nif"); } ///See if the file has the "bsa" extension. -bool isBSA(std::string filename) +bool isBSA(const std::string & filename) { return hasExtension(filename,"bsa"); } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b9279bf91..d1ebcde42 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -81,14 +81,14 @@ opencs_units_noqt (view/world opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton - scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit + scenetooltoggle2 scenetooltexturebrush completerpopup coloreditor colorpickerpopup droplineedit ) opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater + cellwater terraintexturemode ) opencs_units_noqt (view/render @@ -246,7 +246,7 @@ if (DESIRED_QT_VERSION MATCHES 4) target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY}) endif() else() - qt5_use_modules(openmw-cs Widgets Core Network OpenGL) + target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) endif() if (WIN32) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7a825ba39..e45d13aa9 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -320,12 +320,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, 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, SLOT (operationDone (int, bool))); + 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 (operationDone (int, bool))); + connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect ( &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), @@ -437,7 +438,7 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) std::cout << message.mMessage << std::endl; } -void CSMDoc::Document::operationDone (int type, bool failed) +void CSMDoc::Document::operationDone2 (int type, bool failed) { if (type==CSMDoc::State_Saving && !failed) mDirty = false; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d31fd5aca..4c442428e 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -168,13 +168,15 @@ namespace CSMDoc /// 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 operationDone (int type, bool failed); + void operationDone2 (int type, bool failed); void runStateChanged(); diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d2bedc666..d2a4f2a35 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -579,7 +579,7 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) } // 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 && allowPredefined) + else if (token.mType==Token::Type_String) { if (getNextToken()!=Token (Token::Type_EOS)) { diff --git a/apps/opencs/model/prefs/shortcuteventhandler.cpp b/apps/opencs/model/prefs/shortcuteventhandler.cpp index 93e2d85d3..e42cb5da1 100644 --- a/apps/opencs/model/prefs/shortcuteventhandler.cpp +++ b/apps/opencs/model/prefs/shortcuteventhandler.cpp @@ -49,7 +49,7 @@ namespace CSMPrefs ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); if (shortcutListIt != mWidgetShortcuts.end()) { - std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut); + shortcutListIt->second.erase(std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), shortcutListIt->second.end()); } } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 8a9dad7f3..6f64da72e 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -222,7 +222,15 @@ void CSMPrefs::State::declare() EnumValues insertOutsideVisibleCell; insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); - declareCategory ("Scene Drops"); + 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); + + 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"); @@ -230,6 +238,12 @@ void CSMPrefs::State::declare() addValues (insertOutsideCell); declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). addValues (insertOutsideVisibleCell); + declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit). + addValues (landeditOutsideCell); + declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit). + addValues (landeditOutsideVisibleCell); + declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). + setMin (1); declareCategory ("Key Bindings"); diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index e4964d4e3..79cb704bf 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -28,12 +28,13 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId); - // test for empty name and description + // A class should have a name if (class_.mName.empty()) - messages.push_back (std::make_pair (id, class_.mId + " has an empty name")); + messages.push_back (std::make_pair (id, class_.mId + " doesn't have a name")); - if (class_.mDescription.empty()) - messages.push_back (std::make_pair (id, class_.mId + " has an empty description")); + // A playable class should have a description + if (class_.mData.mIsPlayable != 0 && class_.mDescription.empty()) + messages.push_back (std::make_pair (id, class_.mId + " doesn't have a description and it's playable")); // test for invalid attributes for (int i=0; i<2; ++i) diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 3cd4a1b09..be4d37792 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -29,7 +29,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); // check the number of pathgrid points - if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) + if (pathgrid.mData.mS2 < static_cast(pathgrid.mPoints.size())) messages.add (id, pathgrid.mId + " has less points than expected", "", CSMDoc::Message::Severity_Error); else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) messages.add (id, pathgrid.mId + " has more points than expected", "", CSMDoc::Message::Severity_Error); diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 4dd3e1edf..1e86dfe37 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -239,9 +239,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Book& book = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId); @@ -260,9 +258,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Activator& activator = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); @@ -283,9 +279,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Potion& potion = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); @@ -306,9 +300,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Apparatus& apparatus = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId); @@ -329,9 +321,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Armor& armor = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId); @@ -358,9 +348,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Clothing& clothing = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId); @@ -378,9 +366,7 @@ void CSMTools::ReferenceableCheckStage::containerCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Container& container = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); @@ -512,9 +498,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Ingredient& ingredient = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, ingredient.mId); @@ -577,13 +561,8 @@ void CSMTools::ReferenceableCheckStage::lightCheck( messages.push_back (std::make_pair (id, light.mId + " has negative light radius")); if (light.mData.mFlags & ESM::Light::Carry) - { inventoryItemCheck(light, messages, id.toString()); - if (light.mData.mTime == 0) - messages.push_back (std::make_pair (id, light.mId + " has zero duration")); - } - // Check that mentioned scripts exist scriptCheck(light, messages, id.toString()); } @@ -596,9 +575,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Lockpick& lockpick = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId); @@ -619,9 +596,7 @@ void CSMTools::ReferenceableCheckStage::miscCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Miscellaneous& miscellaneous = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId); @@ -644,12 +619,12 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( const ESM::NPC& npc = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId); - short level(npc.mNpdt52.mLevel); - char disposition(npc.mNpdt52.mDisposition); - char reputation(npc.mNpdt52.mReputation); - char rank(npc.mNpdt52.mRank); + short level(npc.mNpdt.mLevel); + char disposition(npc.mNpdt.mDisposition); + char reputation(npc.mNpdt.mReputation); + char rank(npc.mNpdt.mRank); //Don't know what unknown is for - int gold(npc.mNpdt52.mGold); + int gold(npc.mNpdt.mGold); //Detect if player is present if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? @@ -663,36 +638,36 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( return; } - level = npc.mNpdt12.mLevel; - disposition = npc.mNpdt12.mDisposition; - reputation = npc.mNpdt12.mReputation; - rank = npc.mNpdt12.mRank; - gold = npc.mNpdt12.mGold; + level = npc.mNpdt.mLevel; + disposition = npc.mNpdt.mDisposition; + reputation = npc.mNpdt.mReputation; + rank = npc.mNpdt.mRank; + gold = npc.mNpdt.mGold; } else { - if (npc.mNpdt52.mAgility == 0) + if (npc.mNpdt.mAgility == 0) messages.push_back (std::make_pair (id, npc.mId + " agility has zero value")); - if (npc.mNpdt52.mEndurance == 0) + if (npc.mNpdt.mEndurance == 0) messages.push_back (std::make_pair (id, npc.mId + " endurance has zero value")); - if (npc.mNpdt52.mIntelligence == 0) + if (npc.mNpdt.mIntelligence == 0) messages.push_back (std::make_pair (id, npc.mId + " intelligence has zero value")); - if (npc.mNpdt52.mLuck == 0) + if (npc.mNpdt.mLuck == 0) messages.push_back (std::make_pair (id, npc.mId + " luck has zero value")); - if (npc.mNpdt52.mPersonality == 0) + if (npc.mNpdt.mPersonality == 0) messages.push_back (std::make_pair (id, npc.mId + " personality has zero value")); - if (npc.mNpdt52.mStrength == 0) + if (npc.mNpdt.mStrength == 0) messages.push_back (std::make_pair (id, npc.mId + " strength has zero value")); - if (npc.mNpdt52.mSpeed == 0) + if (npc.mNpdt.mSpeed == 0) messages.push_back (std::make_pair (id, npc.mId + " speed has zero value")); - if (npc.mNpdt52.mWillpower == 0) + if (npc.mNpdt.mWillpower == 0) messages.push_back (std::make_pair (id, npc.mId + " willpower has zero value")); } @@ -706,22 +681,14 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( messages.push_back (std::make_pair (id, npc.mId + " has any empty name")); if (npc.mClass.empty()) - { - messages.push_back (std::make_pair (id, npc.mId + " has any empty class")); - } + messages.push_back (std::make_pair (id, npc.mId + " has an empty class")); else if (mClasses.searchId (npc.mClass) == -1) - { messages.push_back (std::make_pair (id, npc.mId + " has invalid class")); - } if (npc.mRace.empty()) - { - messages.push_back (std::make_pair (id, npc.mId + " has any empty race")); - } + messages.push_back (std::make_pair (id, npc.mId + " has an empty race")); else if (mRaces.searchId (npc.mRace) == -1) - { messages.push_back (std::make_pair (id, npc.mId + " has invalid race")); - } if (disposition < 0) messages.push_back (std::make_pair (id, npc.mId + " has negative disposition")); @@ -823,7 +790,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( { //checking of health if (weapon.mData.mHealth <= 0) - messages.push_back (std::make_pair (id, weapon.mId + " has non-positivie health")); + messages.push_back (std::make_pair (id, weapon.mId + " has non-positive health")); if (weapon.mData.mReach < 0) messages.push_back (std::make_pair (id, weapon.mId + " has negative reach")); @@ -842,9 +809,7 @@ void CSMTools::ReferenceableCheckStage::probeCheck( const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Probe& probe = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId); diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index 6a059bee2..3dbd3ef11 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -27,7 +27,7 @@ void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) - messages.push_back (std::make_pair (id, "Maximum range larger than minimum range")); + messages.push_back (std::make_pair (id, "Minimum range larger than maximum range")); /// \todo check, if the sound file exists } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 3b2c80263..445db53af 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -61,12 +61,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); - std::vector mandatoryIds; // I want C++11, damn it! - mandatoryIds.push_back ("Day"); - mandatoryIds.push_back ("DaysPassed"); - mandatoryIds.push_back ("GameHour"); - mandatoryIds.push_back ("Month"); - mandatoryIds.push_back ("PCRace"); + std::vector mandatoryIds {"Day", "DaysPassed", "GameHour", "Month", "PCRace"}; mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(), CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index d609a6253..df37afe60 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -88,6 +88,7 @@ namespace CSMWorld Display_UnsignedInteger8, Display_Integer, Display_Float, + Display_Double, Display_Var, Display_GmstVarType, Display_GlobalVarType, diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index f1025acb9..4ad447b0a 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1371,7 +1371,7 @@ namespace CSMWorld RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) : Column ( (door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index, - ColumnBase::Display_Float), mPosition (position), mIndex (index) {} + ColumnBase::Display_Double), mPosition (position), mIndex (index) {} virtual QVariant get (const Record& record) const { diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index ec010ba36..109708ab0 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -66,7 +66,7 @@ namespace CSMWorld { ColumnId_SleepForbidden, "Sleep Forbidden" }, { ColumnId_InteriorWater, "Interior Water" }, { ColumnId_InteriorSky, "Interior Sky" }, - { ColumnId_Model, "Model" }, + { ColumnId_Model, "Model/Animation" }, { ColumnId_Script, "Script" }, { ColumnId_Icon, "Icon" }, { ColumnId_Weight, "Weight" }, @@ -105,7 +105,7 @@ namespace CSMWorld { ColumnId_Respawn, "Respawn" }, { ColumnId_CreatureType, "Creature Type" }, { ColumnId_SoulPoints, "Soul Points" }, - { ColumnId_OriginalCreature, "Original Creature" }, + { ColumnId_ParentCreature, "Parent Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, { ColumnId_Swims, "Swims" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 15018795c..f9ba5725a 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -99,7 +99,7 @@ namespace CSMWorld ColumnId_Respawn = 84, ColumnId_CreatureType = 85, ColumnId_SoulPoints = 86, - ColumnId_OriginalCreature = 87, + ColumnId_ParentCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, // unused diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 2216d5ca6..053754943 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -962,6 +962,29 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base return mReader->getRecordCount(); } +void CSMWorld::Data::loadFallbackEntries() +{ + // Load default marker definitions, if game files do not have them for some reason + std::pair markers[] = { + 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") + }; + + for (const std::pair marker : markers) + { + if (mReferenceables.searchId (marker.first)==-1) + { + CSMWorld::Record record; + record.mBase = ESM::Static(marker.first, marker.second); + record.mState = CSMWorld::RecordBase::State_BaseOnly; + mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static); + } + } +} + bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { if (!mReader) @@ -983,6 +1006,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) mReader = 0; mDialogue = 0; + + loadFallbackEntries(); + return true; } diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 8a3667ea1..1b975f430 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -144,6 +144,8 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); + void loadFallbackEntries(); + public: Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, diff --git a/apps/opencs/model/world/landtexturetableproxymodel.cpp b/apps/opencs/model/world/landtexturetableproxymodel.cpp index cf33fab9e..e064bbe8a 100644 --- a/apps/opencs/model/world/landtexturetableproxymodel.cpp +++ b/apps/opencs/model/world/landtexturetableproxymodel.cpp @@ -11,11 +11,6 @@ namespace CSMWorld bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { - int columnIndex = mSourceModel->findColumnIndex(Columns::ColumnId_Modification); - QModelIndex index = mSourceModel->index(sourceRow, columnIndex); - if (mSourceModel->data(index).toInt() != RecordBase::State_ModifiedOnly) - return false; - return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent); } } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index e8f921580..5ac9ecb18 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -914,7 +914,7 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable (const RefIdColumn* col ESM::NPC npc = record.get(); // store the whole struct - npc.mNpdt52 = + npc.mNpdt = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); @@ -928,7 +928,7 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTab // return the whole struct std::vector wrap; - wrap.push_back(record.get().mNpdt52); + wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } @@ -939,7 +939,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; if (subColIndex == 0) return subRowIndex; @@ -966,7 +966,7 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedData (const RefIdColumn *colu Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subColIndex == 1) switch(subRowIndex) @@ -1021,7 +1021,7 @@ void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable (const RefIdColumn* column, ESM::NPC npc = record.get(); // store the whole struct - npc.mNpdt52 = + npc.mNpdt = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); @@ -1035,7 +1035,7 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable ( // return the whole struct std::vector wrap; - wrap.push_back(record.get().mNpdt52); + wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } @@ -1046,7 +1046,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); @@ -1065,7 +1065,7 @@ void CSMWorld::NpcSkillsRefIdAdapter::setNestedData (const RefIdColumn *column, Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); @@ -1130,30 +1130,30 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column if (autoCalc) switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt12.mLevel); + 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 QVariant(QVariant::UserType); - case 5: return static_cast(record.get().mNpdt12.mDisposition); - case 6: return static_cast(record.get().mNpdt12.mReputation); - case 7: return static_cast(record.get().mNpdt12.mRank); - case 8: return record.get().mNpdt12.mGold; + case 5: return static_cast(record.get().mNpdt.mDisposition); + case 6: return static_cast(record.get().mNpdt.mReputation); + case 7: return static_cast(record.get().mNpdt.mRank); + case 8: return record.get().mNpdt.mGold; case 9: return record.get().mPersistent == true; default: return QVariant(); // throw an exception here? } else switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt52.mLevel); - case 1: return static_cast(record.get().mNpdt52.mFactionID); - case 2: return static_cast(record.get().mNpdt52.mHealth); - case 3: return static_cast(record.get().mNpdt52.mMana); - case 4: return static_cast(record.get().mNpdt52.mFatigue); - case 5: return static_cast(record.get().mNpdt52.mDisposition); - case 6: return static_cast(record.get().mNpdt52.mReputation); - case 7: return static_cast(record.get().mNpdt52.mRank); - case 8: return record.get().mNpdt52.mGold; + case 0: return static_cast(record.get().mNpdt.mLevel); + case 1: return static_cast(record.get().mNpdt.mFactionID); + case 2: return static_cast(record.get().mNpdt.mHealth); + case 3: return static_cast(record.get().mNpdt.mMana); + case 4: return static_cast(record.get().mNpdt.mFatigue); + case 5: return static_cast(record.get().mNpdt.mDisposition); + case 6: return static_cast(record.get().mNpdt.mReputation); + case 7: return static_cast(record.get().mNpdt.mRank); + case 8: return record.get().mNpdt.mGold; case 9: return record.get().mPersistent == true; default: return QVariant(); // throw an exception here? } @@ -1171,30 +1171,30 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; case 1: return; case 2: return; case 3: return; case 4: return; - case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 5: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt.mGold = value.toInt(); break; case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; - case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; - case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; - case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; - case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; - case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; + case 1: npc.mNpdt.mFactionID = static_cast(value.toInt()); break; + case 2: npc.mNpdt.mHealth = static_cast(value.toInt()); break; + case 3: npc.mNpdt.mMana = static_cast(value.toInt()); break; + case 4: npc.mNpdt.mFatigue = static_cast(value.toInt()); break; + case 5: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt.mGold = value.toInt(); break; case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } @@ -1368,15 +1368,15 @@ QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn const ESM::Creature& creature = record.get(); - if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) + if (subRowIndex < 0 || subRowIndex > 2) throw std::runtime_error ("index out of range"); if (subColIndex == 0) return subRowIndex + 1; - else if (subColIndex < 3) // 1 or 2 + else if (subColIndex == 1 || subColIndex == 2) return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; else - return QVariant(); // throw an exception here? + throw std::runtime_error ("index out of range"); } void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 44a6ce07d..6716c7240 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList, @@ -331,7 +331,7 @@ CSMWorld::RefIdCollection::RefIdCollection() creatureColumns.mType = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); creatureColumns.mScale = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_ParentCreature, ColumnBase::Display_Creature)); creatureColumns.mOriginal = &mColumns.back(); static const struct diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 38386f6da..8d7a7761e 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -64,21 +64,21 @@ namespace static const TypeData sIdArg[] = { - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./globvar.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./GMST.png" }, + { 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", ":./land.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", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, + { 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", 0 }, @@ -93,7 +93,7 @@ namespace { 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", ":./creature.png" }, + "Creature Levelled List", ":./leveled-creature.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", ":./leveled-item.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, @@ -109,20 +109,20 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", 0 }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", 0 }, + { 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", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, + { 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, "LandTexture", ":./land-texture.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 713295d70..49a53e179 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -167,7 +167,7 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, delete iter->second; mDocuments.erase (iter); } - else if (!completed && !error.empty()) + else { iter->second->abort (error); // Leave the window open for now (wait for the user to close it) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 67ff50dab..78f18b3a1 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -106,7 +106,7 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) /// \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 advice to make backups regularly if you are working with live data.
"); + 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); diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index fae609af5..4b14e29bf 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -92,7 +92,7 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) } CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference | Mask_Terrain, "Instance editing", +: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), mDragAxis (-1), mLocked (false), mUnitScaleDist(1) { @@ -104,14 +104,14 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) { mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); mSubMode->addButton (new InstanceMoveMode (this), "move"); - mSubMode->addButton (":placeholder", "rotate", + 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 (":placeholder", "scale", + 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
  • " @@ -551,7 +551,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) if (noCell) { - std::string mode = CSMPrefs::get()["Scene Drops"]["outside-drop"].toString(); + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString(); // target cell does not exist if (mode=="Discard") @@ -585,7 +585,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) { // target cell exists, but is not shown std::string mode = - CSMPrefs::get()["Scene Drops"]["outside-visible-drop"].toString(); + CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString(); if (mode=="Discard") return; diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index fe58b581b..723af811d 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -2,7 +2,7 @@ #include "instancemovemode.hpp" CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) -: ModeButton (QIcon (QPixmap (":placeholder")), +: 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
    • " diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 4a745195b..1d1a7cd17 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -21,6 +21,7 @@ #include "mask.hpp" #include "cameracontroller.hpp" #include "cellarrow.hpp" +#include "terraintexturemode.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { @@ -136,7 +137,7 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), "terrain-shape"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain texture editing"), + new TerrainTextureMode (this, tool), "terrain-texture"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index cf0967e47..b5ccda5ad 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -13,7 +13,7 @@ namespace CSVRender , mWorldspaceWidget(worldspaceWidget) , mInteractionMask(interactionMask) { - addButton(":placeholder", "cube-centre", + addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" "
      • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "(invert selection state) from the centre of the selection cube outwards
      • " @@ -22,7 +22,7 @@ namespace CSVRender "starting on an instance will have the same effect" "
      " "Not implemented yet"); - addButton(":placeholder", "cube-corner", + addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" "
      • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "(invert selection state) from one corner of the selection cube to the opposite corner
      • " @@ -31,7 +31,7 @@ namespace CSVRender "starting on an instance will have the same effect" "
      " "Not implemented yet"); - addButton(":placeholder", "sphere", + addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" "
      • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "(invert selection state) from the centre of the selection sphere outwards
      • " diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp new file mode 100644 index 000000000..376258c5e --- /dev/null +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -0,0 +1,542 @@ +#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/columnbase.hpp" +#include "../../model/world/commandmacro.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/landtexture.hpp" +#include "../../model/world/resourcetable.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.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, QWidget *parent) +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent), + mBrushTexture("L0#0"), + mBrushSize(0), + mBrushShape(0), + mTextureBrushScenetool(0) +{ +} + +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(int)), this, SLOT(setBrushShape(int))); + 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))); + } + + EditMode::activate(toolbar); + toolbar->addTool (mTextureBrushScenetool); +} + +void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar) +{ + if(mTextureBrushScenetool) + { + toolbar->removeTool (mTextureBrushScenetool); + delete mTextureBrushScenetool; + mTextureBrushScenetool = 0; + } + EditMode::deactivate(toolbar); +} + +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 == true) + { + undoStack.beginMacro ("Edit texture records"); + if(allowLandTextureEditing(mCellId)==true) + { + undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); + editTerrainTextureGrid(hit); + } + undoStack.endMacro(); + } +} + +void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit) +{ +} + +void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) +{ +} + +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(); + + CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); + int index = landtexturesCollection.searchId(mBrushTexture); + + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + { + undoStack.beginMacro ("Edit texture records"); + if(allowLandTextureEditing(mCellId)==true && hit.hit == true) + { + 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) +{ + return false; +} + +bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) +{ + return false; +} + +void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +{ + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + 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 == true) + { + editTerrainTextureGrid(hit); + } +} + +void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) { + 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(); + } +} + +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)==true) {} + + 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.5)); + int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.5)); + if (xHitInCell < 0) + { + xHitInCell = xHitInCell + landTextureSize; + cellX = cellX - 1; + } + if (yHitInCell > 15) + { + yHitInCell = yHitInCell - landTextureSize; + cellY = cellY + 1; + } + + mCellId = "#" + std::to_string(cellX) + " " + std::to_string(cellY); + if(allowLandTextureEditing(mCellId)==true) {} + + 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 + + float rf = mBrushSize/2; + int r = (mBrushSize/2)+1; + float distance = 0; + + if (mBrushShape == 0) + { + CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + + if(allowLandTextureEditing(mCellId)==true) + { + mNew[yHitInCell*landTextureSize+xHitInCell] = brushInt; + pushEditToCommand(mNew, document, landTable, mCellId); + } + } + + if (mBrushShape == 1) + { + 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 = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell); + if(allowLandTextureEditing(iteratedCellId)==true) + { + CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + 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) + { + mNew[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) mNew[j*landTextureSize+i] = brushInt; + } + } + } + pushEditToCommand(mNew, document, landTable, iteratedCellId); + } + } + } + } + + if (mBrushShape == 2) + { + 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 = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell); + if(allowLandTextureEditing(iteratedCellId)==true) + { + CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + 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(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); + distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + if (distance < rf) mNew[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); + distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + if (distance < rf) mNew[j*landTextureSize+i] = brushInt; + } + } + } + pushEditToCommand(mNew, document, landTable, iteratedCellId); + } + } + } + } + + if (mBrushShape == 3) + { + // Not implemented + } + +} + +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::TouchLandCommand(landTable, ltexTable, cellId)); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +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& e) + { + 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::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; +} + +void CSVRender::TerrainTextureMode::setBrushShape(int brushShape) +{ + mBrushShape = brushShape; +} + +void CSVRender::TerrainTextureMode::setBrushTexture(std::string brushTexture) +{ + mBrushTexture = brushTexture; +} diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp new file mode 100644 index 000000000..e1538e243 --- /dev/null +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -0,0 +1,100 @@ +#ifndef CSV_RENDER_TERRAINTEXTUREMODE_H +#define CSV_RENDER_TERRAINTEXTUREMODE_H + +#include "editmode.hpp" + +#include + +#include +#include + +#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" + +namespace CSVWidget +{ + class SceneToolTextureBrush; +} + +namespace CSVRender +{ + + class TerrainTextureMode : public EditMode + { + Q_OBJECT + + public: + + /// \brief Editmode for terrain texture grid + TerrainTextureMode(WorldspaceWidget*, QWidget* parent = nullptr); + + /// \brief Create single command for one-click texture editing + void primaryEditPressed (const WorldspaceHitResult& hit); + + /// \brief Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&); + + void secondarySelectPressed(const WorldspaceHitResult&); + + void activate(CSVWidget::SceneToolbar*); + void deactivate(CSVWidget::SceneToolbar*); + + /// \brief Start texture editing command macro + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + virtual bool primarySelectStartDrag (const QPoint& pos); + virtual bool secondarySelectStartDrag (const QPoint& pos); + + /// \brief Handle texture edit behavior during dragging + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + /// \brief End texture editing command macro + virtual void dragCompleted(const QPoint& pos); + + virtual void dragAborted(); + virtual void dragWheel (int diff, double speedFactor); + virtual void dragMoveEvent (QDragMoveEvent *event); + + /// \brief Handle brush mechanics, maths regarding worldspace hit etc. + void editTerrainTextureGrid (const WorldspaceHitResult& hit); + + /// \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); + + private: + std::string mCellId; + std::string mBrushTexture; + int mBrushSize; + int mBrushShape; + CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool; + + const int cellSize {ESM::Land::REAL_SIZE}; + const int landSize {ESM::Land::LAND_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(int brushShape); + void setBrushTexture(std::string brushShape); + }; +} + + +#endif diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index af53c86f0..084fb87e6 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -445,7 +445,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo // Default placement direction.normalize(); - direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt(); + direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt(); WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; return hit; @@ -648,6 +648,12 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) mDragX = event->posF().x(); mDragY = height() - event->posF().y(); #endif + + if (mDragMode == InteractionType_PrimaryEdit) + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos + } } } else diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 4d1456cd9..a970af168 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -156,6 +156,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setSelectionMode (QAbstractItemView::ExtendedSelection); mProxyModel = new QSortFilterProxyModel (this); + mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); mProxyModel->setSourceModel (mModel); mProxyModel->setSortRole(Qt::UserRole); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 493defa5a..9bada22af 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -3,11 +3,15 @@ #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" @@ -73,6 +77,9 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: 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); @@ -93,6 +100,15 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: 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) @@ -101,6 +117,11 @@ void CSVTools::SearchSubView::setEditLock (bool 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)); @@ -126,3 +147,17 @@ 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"); + } +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index ac0a5a762..c0f3eac84 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -15,6 +15,11 @@ namespace CSMDoc class Document; } +namespace CSVWorld +{ + class TableBottomBox; +} + namespace CSVTools { class ReportTable; @@ -28,6 +33,7 @@ namespace CSVTools CSMDoc::Document& mDocument; CSMTools::Search mSearch; bool mLocked; + CSVWorld::TableBottomBox *mBottom; private: @@ -43,6 +49,8 @@ namespace CSVTools virtual void setEditLock (bool locked); + virtual void setStatusBar (bool show); + private slots: void stateChanged (int state, CSMDoc::Document *document); @@ -52,6 +60,10 @@ namespace CSVTools void replaceRequest(); void replaceAllRequest(); + + void tableSizeUpdate(); + + void operationDone (int type, bool failed); }; } diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp new file mode 100644 index 000000000..2208f88a6 --- /dev/null +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -0,0 +1,379 @@ +#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) +{ + mBrushSizeSlider = new QSlider(Qt::Horizontal); + mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); + mBrushSizeSlider->setTickInterval(10); + mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); + mBrushSizeSlider->setSingleStep(1); + + mBrushSizeSpinBox = new QSpinBox; + mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); + mBrushSizeSpinBox->setSingleStep(1); + + mLayoutSliderSize = new QHBoxLayout; + 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), + mBrushShape(0), + mBrushSize(0), + mBrushTexture("L0#0"), + 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) +{ + mBrushTexture = brushTexture; + + CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); + + int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); + int columnModification = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + int index = landtexturesCollection.searchId(mBrushTexture); + + // Check if texture exists in current plugin + if(landtexturesCollection.getData(index, columnModification).value() == 0) + { + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + + QUndoStack& undoStack = mDocument.getUndoStack(); + + QVariant textureFileNameVariant; + textureFileNameVariant.setValue(landtexturesCollection.getData(index, landTextureFilename).value()); + + std::size_t hashlocation = mBrushTexture.find("#"); + std::string mBrushTexturePlugin = "L0#" + mBrushTexture.substr (hashlocation+1); + int indexPlugin = landtexturesCollection.searchId(mBrushTexturePlugin); + + // Reindex texture if needed + if (indexPlugin != -1 && !landtexturesCollection.getRecord(indexPlugin).isDeleted()) + { + int counter=0; + bool freeIndexFound = false; + do { + const size_t maxCounter = std::numeric_limits::max() - 1; + mBrushTexturePlugin = CSMWorld::LandTexture::createUniqueRecordId(0, counter); + if (landtexturesCollection.searchId(mBrushTexturePlugin) != -1 && landtexturesCollection.getRecord(mBrushTexturePlugin).isDeleted() == 0) counter = (counter + 1) % maxCounter; + else freeIndexFound = true; + } while (freeIndexFound == false); + } + + undoStack.beginMacro ("Add land texture record"); + undoStack.push (new CSMWorld::CloneCommand (ltexTable, mBrushTexture, mBrushTexturePlugin, CSMWorld::UniversalId::Type_LandTexture)); + undoStack.endMacro(); + mBrushTexture = mBrushTexturePlugin; + emit passTextureId(mBrushTexture); + } + + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + { + mBrushTextureLabel = "Selected texture: " + mBrushTexture + " "; + mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); + } else + { + mBrushTextureLabel = "No selected texture or invalid texture"; + mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); + } + + emit passBrushShape(mBrushShape); // update icon +} + +void CSVWidget::TextureBrushWindow::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; + emit passBrushSize(mBrushSize); +} + +void CSVWidget::TextureBrushWindow::setBrushShape() +{ + if(mButtonPoint->isChecked()) mBrushShape = 0; + if(mButtonSquare->isChecked()) mBrushShape = 1; + if(mButtonCircle->isChecked()) mBrushShape = 2; + if(mButtonCustom->isChecked()) mBrushShape = 3; + 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(int)), this, SLOT(setButtonIcon(int))); + 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(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); +#else + mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch); +#endif + mTable->setSelectionMode (QAbstractItemView::NoSelection); + + layout->addWidget (mTable); + + connect (mTable, SIGNAL (clicked (const QModelIndex&)), + this, SLOT (clicked (const QModelIndex&))); + +} + +void CSVWidget::SceneToolTextureBrush::setButtonIcon (int brushShape) +{ + QString tooltip = "Change brush settings

        Currently selected: "; + + switch (brushShape) + { + case 0: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + tooltip += mTextureBrushWindow->toolTipPoint; + break; + + case 1: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + tooltip += mTextureBrushWindow->toolTipSquare; + break; + + case 2: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + tooltip += mTextureBrushWindow->toolTipCircle; + break; + + case 3: + + 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(); +} diff --git a/apps/opencs/view/widget/scenetooltexturebrush.hpp b/apps/opencs/view/widget/scenetooltexturebrush.hpp new file mode 100644 index 000000000..4669f432e --- /dev/null +++ b/apps/opencs/view/widget/scenetooltexturebrush.hpp @@ -0,0 +1,133 @@ +#ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H +#define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scenetool.hpp" + +#include "../../model/doc/document.hpp" + +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 = 0); + 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: + int mBrushShape; + int mBrushSize; + std::string mBrushTexture; + 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(int 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); + + virtual void showPanel (const QPoint& position); + void updatePanel (); + + void dropEvent (QDropEvent *event); + void dragEnterEvent (QDragEnterEvent *event); + + friend class CSVRender::TerrainTextureMode; + + public slots: + void setButtonIcon(int brushShape); + void updateBrushHistory (const std::string& mBrushTexture); + void clicked (const QModelIndex& index); + virtual void activate(); + + signals: + void passEvent(QDropEvent *event); + void passEvent(QDragEnterEvent *event); + void passTextureId(std::string brushTexture); + }; +} + +#endif diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 40a24bf65..9fea7b303 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -57,12 +57,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, // left section mPrevButton = new QToolButton (this); - mPrevButton->setIcon(QIcon(":/go-previous.png")); + mPrevButton->setIcon(QIcon(":record-previous")); mPrevButton->setToolTip ("Switch to previous record"); buttonsLayout->addWidget (mPrevButton, 0); mNextButton = new QToolButton (this); - mNextButton->setIcon(QIcon(":/go-next.png")); + mNextButton->setIcon(QIcon(":/record-next")); mNextButton->setToolTip ("Switch to next record"); buttonsLayout->addWidget (mNextButton, 1); @@ -72,7 +72,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton (this); - previewButton->setIcon(QIcon(":/edit-preview.png")); + previewButton->setIcon(QIcon(":edit-preview")); previewButton->setToolTip ("Open a preview of this record"); buttonsLayout->addWidget(previewButton); connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); @@ -89,22 +89,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, // right section mCloneButton = new QToolButton (this); - mCloneButton->setIcon(QIcon(":/edit-clone.png")); + mCloneButton->setIcon(QIcon(":edit-clone")); mCloneButton->setToolTip ("Clone record"); buttonsLayout->addWidget(mCloneButton); mAddButton = new QToolButton (this); - mAddButton->setIcon(QIcon(":/add.png")); + mAddButton->setIcon(QIcon(":edit-add")); mAddButton->setToolTip ("Add new record"); buttonsLayout->addWidget(mAddButton); mDeleteButton = new QToolButton (this); - mDeleteButton->setIcon(QIcon(":/edit-delete.png")); + mDeleteButton->setIcon(QIcon(":edit-delete")); mDeleteButton->setToolTip ("Delete record"); buttonsLayout->addWidget(mDeleteButton); mRevertButton = new QToolButton (this); - mRevertButton->setIcon(QIcon(":/edit-undo.png")); + mRevertButton->setIcon(QIcon(":edit-undo")); mRevertButton->setToolTip ("Revert record"); buttonsLayout->addWidget(mRevertButton); diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index 12d545339..4a229657d 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -27,7 +27,7 @@ CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory() static const char *sIcons[] = { - ":./base.png", ":./modified.png", ":./added.png", ":./removed.png", ":./removed.png", 0 + ":list-base", ":list-modified", ":list-added", ":list-removed", ":list-removed", 0 }; for (int i=0; sIcons[i]; ++i) diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 1363f489a..e939b9baf 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -63,19 +63,9 @@ std::string CSVWorld::ReferenceCreator::getErrors() const std::string cell = mCell->text().toUtf8().constData(); if (cell.empty()) - { - if (!errors.empty()) - errors += "
        "; - errors += "Missing Cell ID"; - } else if (getData().getCells().searchId (cell)==-1) - { - if (!errors.empty()) - errors += "
        "; - errors += "Invalid Cell ID"; - } return errors; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 34ecd57d0..986717cb2 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -764,10 +764,8 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector idToDrag; - foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. - { + for (QModelIndex& it : selectedRows) idToDrag.push_back (getUniversalId (it.row())); - } return idToDrag; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index cfde5c694..f6b060a8f 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -28,6 +28,12 @@ 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" }; @@ -178,10 +184,17 @@ 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(); } @@ -210,7 +223,10 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi } if (changed) + { + mStatusMessage = ""; updateStatus(); + } } void CSVWorld::TableBottomBox::positionChanged (int row, int column) diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 5402c466e..baa68087b 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -39,6 +39,7 @@ namespace CSVWorld bool mHasPosition; int mRow; int mColumn; + QString mStatusMessage; private: @@ -73,6 +74,8 @@ namespace CSVWorld /// /// \note The BotomBox does not partake in the deletion of records. + void setStatusMessage (const QString& message); + signals: void requestFocus (const std::string& id); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index efba1ea82..eab37e1bf 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -233,6 +233,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return dsb; } + case CSMWorld::ColumnBase::Display_Double: + { + DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); + dsb->setRange(-FLT_MAX, FLT_MAX); + dsb->setSingleStep(0.01f); + dsb->setDecimals(6); + return dsb; + } + case CSMWorld::ColumnBase::Display_LongString: { QPlainTextEdit *edit = new QPlainTextEdit(parent); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e869e2915..e64998077 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -89,7 +89,7 @@ add_openmw_dir (mwmechanics ) add_openmw_dir (mwstate - statemanagerimp charactermanager character + statemanagerimp charactermanager character quicksavemanager ) add_openmw_dir (mwbase @@ -222,4 +222,3 @@ endif (MSVC) if (WIN32) INSTALL(TARGETS openmw RUNTIME DESTINATION ".") endif (WIN32) - diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 3f28afa1b..d234a369d 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -35,28 +35,11 @@ int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobjec return SDL_ShowCursor(SDL_QUERY); } - -int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, - jobject obj) { - +int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); - SDL_Android_Init(env, cls); - - SDL_SetMainReady(); - // 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"); - /* Run the application code! */ - - int status; - - status = main(argcData+1, argvData); - releaseArgv(); - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ - - return status; + return 0; } - diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index a9afae786..7bace3790 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -39,6 +39,7 @@ namespace MWBase class ResponseCallback { public: + virtual ~ResponseCallback() = default; virtual void addResponse(const std::string& title, const std::string& text) = 0; }; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index e40bf56bb..f15a86918 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -239,7 +239,7 @@ namespace MWBase 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 std::string& ownerid) = 0; + 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; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f72f74c53..23353cbfe 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -492,12 +492,14 @@ namespace MWBase virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; + virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; + virtual const std::vector& getContentFiles() const = 0; virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; - // Are we in an exterior or pseudo-exterior cell and it's night? - virtual bool isDark() const = 0; + // Allow NPCs to use torches? + virtual bool useTorches() const = 0; virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; @@ -563,6 +565,8 @@ namespace MWBase virtual bool isPlayerInJail() 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; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b6a46cff8..1d51a7830 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -139,24 +139,21 @@ namespace MWClass const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); + 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; - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + const std::string keyId = ptr.getCellRef().getKey(); + if (!keyId.empty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { hasKey = true; - keyName = it->getClass().getName(*it); + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 903ec4958..eba87a47b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -114,7 +114,7 @@ namespace MWClass const std::string lockedSound = "LockedDoor"; const std::string trapActivationSound = "Disarm Trap Fail"; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); @@ -135,21 +135,14 @@ namespace MWClass animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing } - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); + const std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - keyName = it->getClass().getName(*it); - break; - } + hasKey = true; + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index f9056b75d..aeeb89a72 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -233,24 +233,6 @@ namespace MWClass if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) return std::make_pair(0,""); - const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); - MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(weapon == invStore.end()) - return std::make_pair(1,""); - - /// \todo the 2h check is repeated many times; put it in a function - if(weapon->getTypeName() == typeid(ESM::Weapon).name() && - (weapon->get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)) - { - return std::make_pair(3,""); - } return std::make_pair(1,""); } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index a68176226..62b15bc86 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,6 +1,7 @@ #include "misc.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -83,8 +84,24 @@ namespace MWClass if (ptr.getCellRef().getSoul() != "") { - const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mRef.getSoul()); - value *= creature->mData.mSoul; + 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; @@ -148,8 +165,9 @@ namespace MWClass if (ref->mRef.getSoul() != "") { - const ESM::Creature *creature = store.get().find(ref->mRef.getSoul()); - info.caption += " (" + creature->mName + ")"; + const ESM::Creature *creature = store.get().search(ref->mRef.getSoul()); + if (creature) + info.caption += " (" + creature->mName + ")"; } std::string text; @@ -210,7 +228,7 @@ namespace MWClass std::shared_ptr Miscellaneous::use (const MWWorld::Ptr& ptr) const { - if (ptr.getCellRef().getSoul().empty()) + 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)); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 134bbf943..8e8b5c3ad 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -312,40 +312,40 @@ namespace MWClass int gold=0; if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - gold = ref->mBase->mNpdt52.mGold; + gold = ref->mBase->mNpdt.mGold; for (unsigned int i=0; i< ESM::Skill::Length; ++i) - data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); + data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]); - data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength); - data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence); - data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower); - data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility); - data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed); - data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance); - data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality); - data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck); + 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->mNpdt52.mHealth); - data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana); - data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue); + 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->mNpdt52.mLevel); - data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition); - data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation); + 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->mNpdt12.mGold; + gold = ref->mBase->mNpdt.mGold; for (int i=0; i<3; ++i) data->mNpcStats.setDynamic (i, 10); - data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel); - data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition); - data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); + 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); @@ -406,7 +406,7 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); - getInventoryStore(ptr).autoEquip(ptr); + getInventoryStore(ptr).autoEquip(ptr); } } @@ -988,7 +988,7 @@ namespace MWClass const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + gmst.fJumpEncumbranceMultiplier->getFloat() * - (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); + (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = static_cast(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); float b = 0.0f; @@ -1327,10 +1327,7 @@ namespace MWClass int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); - if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - return ref->mBase->mNpdt52.mGold; - else - return ref->mBase->mNpdt12.mGold; + return ref->mBase->mNpdt.mGold; } bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 9fb4a9767..e59567aac 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -325,7 +325,10 @@ namespace MWClass // add reach and attack speed for melee weapon if (ref->mBase->mData.mType < 9 && Settings::Manager::getBool("show melee info", "Game")) { - text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mReach, "#{sRange}"); + // 64 game units = 1 yard = 3 ft, display value in feet + const float combatDistance = store.get().find("fCombatDistance")->getFloat() * ref->mBase->mData.mReach; + text += MWGui::ToolTips::getWeightString(combatDistance*3/64, "#{sRange}"); + text += " #{sFeet}"; text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index de9ca83ca..fb492ff3b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -432,7 +432,6 @@ namespace MWDialogue void DialogueManager::addChoice (const std::string& text, int choice) { mIsInChoice = true; - mChoices.push_back(std::make_pair(text, choice)); } diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 5a9237cea..6f16cf076 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -892,7 +892,7 @@ protected: public: typedef TypesetBookImpl::StyleImpl Style; - typedef std::map ActiveTextFormats; + typedef std::map > ActiveTextFormats; int mViewTop; int mViewBottom; @@ -1048,7 +1048,7 @@ public: { if (mNode != NULL) i->second->destroyDrawItem (mNode); - delete i->second; + i->second.reset(); } mActiveTextFormats.clear (); @@ -1115,11 +1115,11 @@ public: if (j == this_->mActiveTextFormats.end ()) { - TextFormat * textFormat = new TextFormat (Font, this_); + std::unique_ptr textFormat(new TextFormat (Font, this_)); textFormat->mTexture = Font->getTextureFont (); - j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first; + j = this_->mActiveTextFormats.insert (std::make_pair (Font, std::move(textFormat))).first; } j->second->mCountVertex += run.mPrintableChars * 6; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 14bbe81ef..450799f29 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -635,6 +635,11 @@ namespace MWGui void DialogueWindow::onChoiceActivated(int id) { + if (mGoodbye) + { + onGoodbyeActivated(); + return; + } MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); updateTopics(); } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 3616b8b62..f7764e0f1 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -339,8 +339,7 @@ namespace MWGui 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.getCellRef().getRefId())) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->getString(); if (msg.find("%s") != std::string::npos) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index a9319048e..16568e2f3 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -31,6 +31,14 @@ namespace MWGui boost::algorithm::replace_all(mText, "\r", ""); + // vanilla game does not show any text after last
        tag. + const std::string lowerText = Misc::StringUtils::lowerCase(mText); + int index = lowerText.rfind("
        "); + if (index == -1) + mText = ""; + else + mText = mText.substr(0, index+4); + registerTag("br", Event_BrTag); registerTag("p", Event_PTag); registerTag("img", Event_ImgTag); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 7bf8ad025..4acf61d93 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -129,6 +129,12 @@ namespace MWGui 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(); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index a31eb9c76..6b5be0314 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -16,8 +16,13 @@ namespace { if (count == 1) return ""; - if (count > 9999) - return MyGUI::utility::toString(int(count/1000.f)) + "k"; + + 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); } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index e8aa23158..1075239fa 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -244,7 +244,7 @@ BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, textColours.journalTopicOver, - textColours.journalTopicPressed, (uint32_t) ch); + textColours.journalTopicPressed, (Utf8Stream::UnicodeChar) ch); if (i == 13) typesetter->sectionBreak (); @@ -274,7 +274,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () sprintf(buffer, "( %c%c )", ch[0], ch[1]); Utf8Stream stream ((char*) ch); - uint32_t first = stream.peek(); + Utf8Stream::UnicodeChar first = stream.peek(); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 6ff68c9c5..63b48eab1 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -7,7 +7,6 @@ #include #include -#include #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -307,39 +306,22 @@ struct JournalViewModelImpl : JournalViewModel visitor (toUtf8Span (topic.getName())); } - void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const + void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { Utf8Stream stream (i->first.c_str()); - uint32_t first = toUpper(stream.peek()); + Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek()); - if (first != character) + if (first != Misc::StringUtils::toLowerUtf8(character)) continue; visitor (i->second.getName()); } } - static uint32_t toUpper(uint32_t ch) - { - // Russian alphabet - if (ch >= 0x0430 && ch < 0x0450) - ch -= 0x20; - - // Cyrillic IO character - if (ch == 0x0451) - ch -= 0x50; - - // Latin alphabet - if (ch >= 0x61 && ch < 0x80) - ch -= 0x20; - - return ch; - } - struct TopicEntryImpl : BaseEntry { MWDialogue::Topic const & mTopic; diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 01dcb49de..fa4090225 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace MWGui { /// View-Model for the journal GUI @@ -76,7 +78,7 @@ namespace MWGui virtual void visitTopicName (TopicId topicId, std::function visitor) const = 0; /// walks over the topics whose names start with the character - virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0; + 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; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 4b7a789e8..0776706d8 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -616,7 +616,7 @@ namespace if (page+2 < book->pageCount()) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound("book page", true); page += 2; updateShowingPages (); @@ -634,7 +634,7 @@ namespace if(page >= 2) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound("book page", true); page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index d27185204..cde8a17cc 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -14,6 +14,9 @@ namespace MWGui bool shouldAcceptKeyFocus(MyGUI::Widget* w) { + if (w && w->getUserString("IgnoreTabKey") == "y") + return false; + return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); } @@ -39,6 +42,7 @@ void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& resu KeyboardNavigation::KeyboardNavigation() : mCurrentFocus(nullptr) , mModalWindow(nullptr) + , mEnabled(true) { MyGUI::WidgetManager::getInstance().registerUnlinker(this); } @@ -101,6 +105,9 @@ bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) void KeyboardNavigation::onFrame() { + if (!mEnabled) + return; + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (focus == mCurrentFocus) @@ -150,6 +157,11 @@ void KeyboardNavigation::setModalWindow(MyGUI::Widget *window) mModalWindow = window; } +void KeyboardNavigation::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + enum Direction { D_Left, @@ -162,6 +174,9 @@ enum Direction bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) { + if (!mEnabled) + return false; + switch (key.getValue()) { case MyGUI::KeyCode::ArrowLeft: diff --git a/apps/openmw/mwgui/keyboardnavigation.hpp b/apps/openmw/mwgui/keyboardnavigation.hpp index 728f16a3d..7caf25690 100644 --- a/apps/openmw/mwgui/keyboardnavigation.hpp +++ b/apps/openmw/mwgui/keyboardnavigation.hpp @@ -28,6 +28,8 @@ namespace MWGui void setModalWindow(MyGUI::Widget* window); + void setEnabled(bool enabled); + private: bool switchFocus(int direction, bool wrap); @@ -40,6 +42,8 @@ namespace MWGui MyGUI::Widget* mCurrentFocus; MyGUI::Widget* mModalWindow; + + bool mEnabled; }; } diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 6da7b0905..08192625f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -81,6 +81,47 @@ namespace MWGui 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) + { + ItemWidget* button = mQuickKeyButtons[i]; + int type = mAssigned[i]; + + switch (type) + { + case Type_Unassigned: + case Type_HandToHand: + case Type_Magic: + break; + case Type_Item: + case Type_MagicItem: + { + MWWorld::Ptr item = *button->getUserData(); + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) + { + // Try searching for a compatible replacement + std::string id = item.getCellRef().getRefId(); + + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); + break; + } + } + } + } + + } + void QuickKeysMenu::unassign(ItemWidget* key, int index) { key->clearUserStrings(); @@ -122,12 +163,10 @@ namespace MWGui assert(index != -1); mSelectedIndex = index; - { - // open assign dialog - if (!mAssignDialog) - mAssignDialog = new QuickKeysMenuAssign(this); - mAssignDialog->setVisible (true); - } + // open assign dialog + if (!mAssignDialog) + mAssignDialog = new QuickKeysMenuAssign(this); + mAssignDialog->setVisible (true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) @@ -296,21 +335,16 @@ namespace MWGui if (type == Type_Item || type == Type_MagicItem) { MWWorld::Ptr item = *button->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() < 1) + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement std::string id = item.getCellRef().getRefId(); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) - { - item = *it; - button->setUserData(MWWorld::Ptr(item)); - break; - } - } + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); if (item.getRefData().getCount() < 1) { @@ -330,7 +364,12 @@ namespace MWGui MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); if (!spells.hasSpell(spellId)) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + MWBase::Environment::get().getWindowManager()->messageBox ( + "#{sQuickMenu5} " + spell->mName); 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); @@ -493,6 +532,9 @@ namespace MWGui ESM::QuickKeys keys; keys.load(reader); + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + int i=0; for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) { @@ -514,22 +556,7 @@ namespace MWGui case Type_MagicItem: { // Find the item by id - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWWorld::Ptr item; - for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) - { - if (item.isEmpty() || - // Prefer the stack with the lowest remaining uses - !item.getClass().hasItemHealth(*iter) || - iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) - { - item = *iter; - } - } - } + MWWorld::Ptr item = store.findReplacement(id); if (item.isEmpty()) unassign(button, i); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 0070aa55b..b5bc60b19 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -34,6 +34,7 @@ namespace MWGui void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); + void onOpen(); void activateQuickKey(int index); void updateActivatedQuickKey(); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 6213d9e25..83221c4f4 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -15,11 +15,8 @@ namespace MWGui // check if count of the reference has become 0 if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) { - if (!mPtr.isEmpty()) - { - mPtr = MWWorld::Ptr(); - onReferenceUnavailable(); - } + mPtr = MWWorld::Ptr(); + onReferenceUnavailable(); } } } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 9bf6e4385..677ddefb3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -562,8 +562,9 @@ namespace MWGui void SettingsWindow::onOpen() { - updateControlsBox (); + updateControlsBox(); resetScrollbars(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 1ee3cf631..097412b5d 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -208,7 +208,7 @@ namespace MWGui if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() - || base.getCellRef().getSoul() == "")) + || 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; diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index f83b72096..a73d343f9 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -32,10 +32,14 @@ namespace namespace MWGui { + SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter) + : mActor(actor), mFilter(filter) + { + } + SpellModel::SpellModel(const MWWorld::Ptr &actor) : mActor(actor) { - } void SpellModel::update() @@ -48,12 +52,19 @@ namespace MWGui 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) + continue; + Spell newSpell; newSpell.mName = spell->mName; if (spell->mData.mType == ESM::Spell::ST_Spell) @@ -89,6 +100,11 @@ namespace MWGui 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) + continue; + Spell newSpell; newSpell.mItem = item; newSpell.mId = item.getCellRef().getRefId(); diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index 21fbc9a6e..6b10f7127 100644 --- a/apps/openmw/mwgui/spellmodel.hpp +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -35,6 +35,7 @@ namespace MWGui class SpellModel { public: + SpellModel(const MWWorld::Ptr& actor, const std::string& filter); SpellModel(const MWWorld::Ptr& actor); typedef int ModelIndex; @@ -50,6 +51,8 @@ namespace MWGui MWWorld::Ptr mActor; std::vector mSpells; + + std::string mFilter; }; } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 601204aa1..3fe171e4e 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -38,8 +39,12 @@ namespace MWGui getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); + getWidget(mFilterEdit, "FilterEdit"); + + mFilterEdit->setUserString("IgnoreTabKey", "y"); mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); + mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged); setCoord(498, 300, 302, 300); } @@ -64,6 +69,11 @@ namespace MWGui 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(NULL); + updateSpells(); } @@ -82,7 +92,7 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, false); - mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), mFilterEdit->getCaption())); } void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) @@ -167,6 +177,11 @@ namespace MWGui } } + void SpellWindow::onFilterChanged(MyGUI::EditBox *sender) + { + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption())); + } + void SpellWindow::onSpellSelected(const std::string& spellId) { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -202,7 +217,7 @@ namespace MWGui if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) return; - mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), "")); SpellModel::ModelIndex selected = 0; for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index f8fead9ea..ce10770f5 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -32,6 +32,7 @@ namespace MWGui void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); void onSpellSelected(const std::string& spellId); void onModelIndexSelected(SpellModel::ModelIndex index); + void onFilterChanged(MyGUI::EditBox *sender); void onDeleteSpellAccept(); void askDeleteSpell(const std::string& spellId); @@ -41,6 +42,7 @@ namespace MWGui SpellView* mSpellView; SpellIcons* mSpellIcons; + MyGUI::EditBox* mFilterEdit; private: float mUpdateTimer; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 2eeeafe0d..0a9fe1b4a 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -311,8 +311,7 @@ namespace MWGui // check if the player is attempting to sell back an item stolen from this actor for (std::vector::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) { - if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), - mPtr.getCellRef().getRefId())) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), mPtr)) { std::string msg = gmst.find("sNotifyMessage49")->getString(); if (msg.find("%s") != std::string::npos) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 7586bb66a..7b65eb771 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -72,7 +74,10 @@ namespace MWGui MWWorld::ActionTeleport::getFollowersToTeleport(player, followers); // Apply followers cost, in vanilla one follower travels for free - price *= std::max(1, static_cast(followers.size())); + if (Settings::Manager::getBool("charge for every follower travelling", "Game")) + price *= 1 + static_cast(followers.size()); + else + price *= std::max(1, static_cast(followers.size())); MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); toAdd->setEnabled(price <= playerGold); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 61febf315..52575e25c 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -310,8 +310,10 @@ namespace MWGui void WaitDialog::wakeUp () { mSleeping = false; - mTimeAdvancer.stop(); - stopWaiting(); + if (mInterruptAt != -1) + onWaitingInterrupted(); + else + stopWaiting(); } } diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 45767bf01..024a91fc6 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -533,10 +533,10 @@ namespace MWGui } MWScrollBar::MWScrollBar() - : mEnableRepeat(true) - , mRepeatTriggerTime(0.5f) - , mRepeatStepTime(0.1f) - , mIsIncreasing(true) + : mEnableRepeat(true) + , mRepeatTriggerTime(0.5f) + , mRepeatStepTime(0.1f) + , mIsIncreasing(true) { #if MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3,2,2) ScrollBar::setRepeatEnabled(false); diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index a0e7eedde..93440a50a 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -25,7 +25,7 @@ void WindowBase::setVisible(bool visible) if (visible) onOpen(); - else if (wasVisible && !visible) + else if (wasVisible) onClose(); // This is needed as invisible widgets can retain key focus. diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 300f8e39f..927bee88b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -237,7 +237,10 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); + bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI"); mKeyboardNavigation.reset(new KeyboardNavigation()); + mKeyboardNavigation->setEnabled(keyboardNav); + Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); mWindows.push_back(mLoadingScreen); @@ -910,6 +913,9 @@ namespace MWGui updateMap(); + if (!mMap->isVisible()) + mMap->onFrame(frameDuration); + mHud->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration); @@ -1920,6 +1926,7 @@ namespace MWGui { if (soundId.empty()) return; + MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index c440de455..9fe9026ca 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -526,30 +526,29 @@ namespace MWInput isRunning = xAxis > .75 || xAxis < .25 || yAxis > .75 || yAxis < .25; if(triedToMove) resetIdleTime(); - if (actionIsActive(A_MoveLeft)) + if (actionIsActive(A_MoveLeft) && !actionIsActive(A_MoveRight)) { triedToMove = true; mPlayer->setLeftRight (-1); } - else if (actionIsActive(A_MoveRight)) + else if (actionIsActive(A_MoveRight) && !actionIsActive(A_MoveLeft)) { triedToMove = true; mPlayer->setLeftRight (1); } - if (actionIsActive(A_MoveForward)) + if (actionIsActive(A_MoveForward) && !actionIsActive(A_MoveBackward)) { triedToMove = true; mPlayer->setAutoMove (false); mPlayer->setForwardBackward (1); } - else if (actionIsActive(A_MoveBackward)) + else if (actionIsActive(A_MoveBackward) && !actionIsActive(A_MoveForward)) { triedToMove = true; mPlayer->setAutoMove (false); mPlayer->setForwardBackward (-1); } - else if(mPlayer->getAutoMove()) { triedToMove = true; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7a1e7270d..f1bc6907c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr) return !stats.isDead() && !stats.getKnockedDown(); } -void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) +int getBoundItemSlot (const std::string& itemId) { - if (bound) + static std::map boundItemsMap; + if (boundItemsMap.empty()) { - if (actor.getClass().getContainerStore(actor).count(item) == 0) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (actor == MWMechanics::getPlayer() - && rightHand != store.end() && newPtr == *rightHand) - { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - } - } + std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } - else - actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true); + + 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 @@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; @@ -227,6 +235,69 @@ namespace MWMechanics } }; + 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 = *store.getSlot(slot); + + 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); + + store.remove(itemId, 1, actor, true); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); + + if (prevItemId.empty()) + return; + + // 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) + return; + + MWWorld::ActionEquip action(item); + action.execute(actor); + } + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects @@ -391,7 +462,7 @@ namespace MWMechanics { // Player followers and escorters with high fight should not initiate combat with the player or with // other player followers or escorters - if (std::find(playerAllies.begin(), playerAllies.end(), actor1) == playerAllies.end()) + if (!isPlayerFollowerOrEscorter) aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } @@ -756,25 +827,23 @@ namespace MWMechanics float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + std::string itemGmst = it->second; std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + if (it->first == ESM::MagicEffect::BoundGloves) { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundLeftGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); item = MWBase::Environment::get().getWorld()->getStore().get().find( "sMagicBoundRightGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } - else - adjustBoundItem(item, magnitude > 0, ptr); - - if (magnitude > 0) - creatureStats.mBoundItems.insert(it->first); - else - creatureStats.mBoundItems.erase(it->first); } } @@ -900,7 +969,7 @@ namespace MWMechanics stats.setTimeToStartDrowning(fHoldBreathTime); } - void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) + void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip) { bool isPlayer = (ptr == getPlayer()); @@ -922,7 +991,7 @@ namespace MWMechanics } } - if (MWBase::Environment::get().getWorld()->isDark()) + if (mayEquip) { if (torch != inventoryStore.end()) { @@ -931,16 +1000,11 @@ namespace MWMechanics // 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); - - // Also unequip twohanded weapons which conflict with anything in CarriedLeft - if (torch->getClass().canBeEquipped(*torch, ptr).first == 3) - inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, ptr); } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - // If we have a torch and can equip it (left slot free, no - // twohanded weapon in right slot), then equip it now. + // If we have a torch and can equip it, then equip it now. if (heldIter == inventoryStore.end() && torch->getClass().canBeEquipped(*torch, ptr).first == 1) { @@ -997,7 +1061,7 @@ namespace MWMechanics } } - void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) + void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) { MWWorld::Ptr player = getPlayer(); if (ptr != player && ptr.getClass().isNpc()) @@ -1204,6 +1268,9 @@ namespace MWMechanics if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; + // show torches only when there are darkness and no precipitations + bool showTorches = MWBase::Environment::get().getWorld()->useTorches(); + MWWorld::Ptr player = getPlayer(); /// \todo move update logic to Actor class where appropriate @@ -1287,7 +1354,7 @@ namespace MWMechanics } if (iter->first.getClass().isNpc() && iter->first != player) - updateCrimePersuit(iter->first, duration); + updateCrimePursuit(iter->first, duration); if (iter->first != player) { @@ -1302,7 +1369,7 @@ namespace MWMechanics updateNpc(iter->first, duration); if (timerUpdateEquippedLight == 0) - updateEquippedLight(iter->first, updateEquippedLightInterval); + updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } } } @@ -1424,7 +1491,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); break; } - else if (!detected) + else avoidedNotice = true; } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 13641abf4..0de1f4d6c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../mwbase/world.hpp" @@ -26,6 +25,9 @@ namespace MWMechanics { std::map mDeathCount; + void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); + void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); + void updateNpc(const MWWorld::Ptr &ptr, float duration); void adjustMagicEffects (const MWWorld::Ptr& creature); @@ -39,9 +41,9 @@ namespace MWMechanics void updateDrowning (const MWWorld::Ptr& ptr, float duration); - void updateEquippedLight (const MWWorld::Ptr& ptr, float duration); + void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); - void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration); + void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); void killDeadActors (); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 83ebc67d9..06248fa10 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -103,9 +103,10 @@ namespace MWMechanics bool isFleeing(); }; - AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) - {} + AiCombat::AiCombat(const MWWorld::Ptr& actor) + { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + } AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) { diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index e7d74248b..6e1f0c623 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -54,9 +54,6 @@ namespace MWMechanics virtual bool shouldCancelPreviousAi() const { return false; } private: - - int mTargetActorId; - /// Returns true if combat should end bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 103ef32e1..a86d13d75 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -22,28 +22,32 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = actorId; mMaxDist = 450; } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + 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; mMaxDist = 450; } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) - : mActorId(escort->mTargetId), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) , mMaxDist(450) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = escort->mTargetId; + mTargetActorId = escort->mTargetActorId; // 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. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. @@ -78,7 +82,7 @@ namespace MWMechanics 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(mActorId, false); + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; double differenceBetween[3]; @@ -119,18 +123,14 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() const - { - return MWBase::Environment::get().getWorld()->getPtr(mActorId, 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 = mActorId; + escort->mTargetId = mTargetActorRefId; + escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 719582271..82dba960e 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -24,11 +24,11 @@ namespace MWMechanics /// 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); + 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 std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); AiEscort(const ESM::AiSequence::AiEscort* escort); @@ -38,7 +38,6 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; @@ -46,7 +45,6 @@ namespace MWMechanics void fastForward(const MWWorld::Ptr& actor, AiState& state); private: - std::string mActorId; std::string mCellId; float mX; float mY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index fd5f9c7fe..13de01f9a 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -35,32 +35,53 @@ struct AiFollowStorage : AiTemporaryBase int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) +AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) +, 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) +AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mActorId(-1), mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) +, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { + mTargetActorRefId = actorId; } -AiFollow::AiFollow(const std::string &actorId, bool commanded) +AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) +: mAlwaysFollow(false), mCommanded(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), mCommanded(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) : mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) +, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { + mTargetActorRefId = actor.getCellRef().getRefId(); + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) - , mActorRefId(follow->mTargetId), mActorId(-1) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) { -// 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. + mTargetActorRefId = follow->mTargetId; + mTargetActorId = follow->mTargetActorId; + // 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. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. mDuration = 1; else @@ -204,7 +225,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte std::string AiFollow::getFollowedActor() { - return mActorRefId; + return mTargetActorRefId; } AiFollow *MWMechanics::AiFollow::clone() const @@ -228,7 +249,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; - follow->mTargetId = mActorRefId; + follow->mTargetId = mTargetActorRefId; + follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; @@ -241,29 +263,6 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const sequence.mPackages.push_back(package); } -MWWorld::Ptr AiFollow::getTarget() const -{ - if (mActorId == -2) - return MWWorld::Ptr(); - - if (mActorId == -1) - { - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorRefId, false); - if (target.isEmpty()) - { - mActorId = -2; - return target; - } - else - mActorId = target.getClass().getCreatureStats(target).getActorId(); - } - - if (mActorId != -1) - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); - else - return MWWorld::Ptr(); -} - int AiFollow::getFollowIndex() const { return mFollowIndex; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 051a4a2ce..f0d43c9a7 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -25,16 +25,17 @@ namespace MWMechanics class AiFollow : public AiPackage { 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 std::string &ActorId,float duration, float X, float Y, float Z); + 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 std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId, bool commanded=false); + AiFollow(const MWWorld::Ptr& actor, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } virtual bool shouldCancelPreviousAi() const { return !mCommanded; } @@ -66,8 +67,6 @@ namespace MWMechanics float mX; float mY; float mZ; - std::string mActorRefId; - mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 398e84448..e6cca0523 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mTargetActorRefId(""), + mTargetActorId(-1), mRotateOnTheRunChecks(0), mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos() + mShortcutProhibited(false), + mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { - return MWWorld::Ptr(); + if (mTargetActorId == -2) + return MWWorld::Ptr(); + + if (mTargetActorId == -1) + { + 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(); } bool MWMechanics::AiPackage::sideWithTarget() const @@ -99,6 +120,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!isDestReached && mTimer > AI_REACTION_TIME) { + if (actor.getClass().isBipedal(actor)) + openDoors(actor); + bool wasShortcutting = mIsShortcutting; bool destInLOS = false; @@ -188,45 +212,43 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (door != MWWorld::Ptr()) + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (!door.isEmpty() && actor.getClass().isBipedal(actor)) { - // note: AiWander currently does not open doors - if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) - { - if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) - { - MWBase::Environment::get().getWorld()->activate(door, actor); - return; - } - - std::string keyId = door.getCellRef().getKey(); - if (keyId.empty()) - return; + openDoors(actor); + } + else + { + mObstacleCheck.takeEvasiveAction(movement); + } +} - bool hasKey = false; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); +void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) +{ + static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - // make key id lowercase - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) - { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - break; - } - } + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (door == MWWorld::Ptr()) + return; - if (hasKey) - MWBase::Environment::get().getWorld()->activate(door, actor); - } - } - else // any other obstacle (NPC, crate, etc.) + // note: AiWander currently does not open doors + if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) { - mObstacleCheck.takeEvasiveAction(movement); + if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) + { + MWBase::Environment::get().getWorld()->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()) + MWBase::Environment::get().getWorld()->activate(door, actor); } } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 06b4adf61..2b685accc 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -48,7 +48,8 @@ namespace MWMechanics TypeIdPursue = 6, TypeIdAvoidDoor = 7, TypeIdFace = 8, - TypeIdBreathe = 9 + TypeIdBreathe = 9, + TypeIdInternalTravel = 10 }; ///Default constructor @@ -79,6 +80,9 @@ namespace MWMechanics /// 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) virtual bool sideWithTarget() const; @@ -119,6 +123,7 @@ namespace MWMechanics virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + void openDoors(const MWWorld::Ptr& actor); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); @@ -128,6 +133,9 @@ namespace MWMechanics float mTimer; + std::string mTargetActorRefId; + mutable int mTargetActorId; + osg::Vec3f mLastActorPos; short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index fd8b5752a..f46594655 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -15,13 +15,13 @@ namespace MWMechanics { AiPursue::AiPursue(const MWWorld::Ptr& actor) - : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) - : mTargetActorId(pursue->mTargetActorId) { + mTargetActorId = pursue->mTargetActorId; } AiPursue *MWMechanics::AiPursue::clone() const diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index cb93e9636..455b0a2fd 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -40,10 +40,6 @@ namespace MWMechanics virtual bool canCancel() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; } - - private: - - int mTargetActorId; // The actor to pursue }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2a2d598f5..85afbc453 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId) && packageTypeId != AiPackage::TypeIdPursue && packageTypeId != AiPackage::TypeIdAvoidDoor && packageTypeId != AiPackage::TypeIdFace - && packageTypeId != AiPackage::TypeIdBreathe); + && packageTypeId != AiPackage::TypeIdBreathe + && packageTypeId != AiPackage::TypeIdInternalTravel); } void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) @@ -298,7 +299,7 @@ void AiSequence::clear() mPackages.clear(); } -void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) +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"); @@ -307,8 +308,33 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) if (isActualAiPackage(package.getTypeId())) stopCombat(); + // We should return a wandering actor back after combat or pursuit. + // The same thing for actors without AI packages. + // Also there is no point to stack return packages. + int currentTypeId = getTypeId(); + int newTypeId = package.getTypeId(); + if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander + && !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel) + && (newTypeId <= MWMechanics::AiPackage::TypeIdCombat + || newTypeId == MWMechanics::AiPackage::TypeIdPursue)) + { + osg::Vec3f dest; + if (currentTypeId == MWMechanics::AiPackage::TypeIdWander) + { + AiPackage* activePackage = getActivePackage(); + dest = activePackage->getDestination(actor); + } + else + { + dest = actor.getRefData().getPosition().asVec3(); + } + + MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true); + stack(travelPackage, actor, false); + } + // remove previous packages if required - if (package.shouldCancelPreviousAi()) + if (cancelOther && package.shouldCancelPreviousAi()) { for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { @@ -392,6 +418,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const { (*iter)->writeState(sequence); } + + sequence.mLastAiPackage = mLastAiPackage; } void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) @@ -403,7 +431,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) int count = 0; for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) - { + { if (isActualAiPackage(it->mType)) count++; } @@ -462,6 +490,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) mPackages.push_back(package.release()); } + + mLastAiPackage = sequence.mLastAiPackage; } void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41d204ee2..d725409de 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -115,7 +115,7 @@ namespace MWMechanics ///< 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); + 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 **/ diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 36b96101f..72e6ced19 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -28,15 +28,14 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z) - : mX(x),mY(y),mZ(z) + AiTravel::AiTravel(float x, float y, float z, bool hidden) + : mX(x),mY(y),mZ(z),mHidden(hidden) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden) { - } AiTravel *MWMechanics::AiTravel::clone() const @@ -64,7 +63,7 @@ namespace MWMechanics int AiTravel::getTypeId() const { - return TypeIdTravel; + return mHidden ? TypeIdInternalTravel : TypeIdTravel; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) @@ -83,6 +82,7 @@ namespace MWMechanics 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; diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 8c75bded1..c297771d0 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -20,7 +20,7 @@ namespace MWMechanics { public: /// Default constructor - AiTravel(float x, float y, float z); + AiTravel(float x, float y, float z, bool hidden = false); AiTravel(const ESM::AiSequence::AiTravel* travel); /// Simulates the passing of time @@ -38,6 +38,8 @@ namespace MWMechanics float mX; float mY; float mZ; + + bool mHidden; }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8199170dc..ee680159e 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -59,7 +59,7 @@ namespace MWMechanics float mTargetAngleRadians; bool mTurnActorGivingGreetingToFacePlayer; float mReaction; // update some actions infrequently - + AiWander::GreetingState mSaidGreeting; int mGreetingTimer; @@ -70,7 +70,7 @@ namespace MWMechanics bool mIsWanderingManually; bool mCanWanderAlongPathGrid; - + unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors @@ -86,7 +86,7 @@ namespace MWMechanics float mDoorCheckDuration; int mStuckCount; - + AiWanderStorage(): mTargetAngleRadians(0), mTurnActorGivingGreetingToFacePlayer(false), @@ -111,7 +111,7 @@ namespace MWMechanics mIsWanderingManually = isManualWander; } }; - + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) @@ -223,7 +223,7 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); } - + doPerFrameActionsForState(actor, duration, storage, pos); playIdleDialogueRandomly(actor); @@ -298,13 +298,6 @@ namespace MWMechanics if(mDistance && cellChange) mDistance = 0; - // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere - if (mDistance == 0 && !cellChange - && (pos.asVec3() - mInitialActorPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) - { - returnToStartLocation(actor, storage, pos); - } - // Allow interrupting a walking actor to trigger a greeting WanderState& wanderState = storage.mState; if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) @@ -321,7 +314,7 @@ namespace MWMechanics { setPathToAnAllowedNode(actor, storage, pos); } - } + } } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { completeManualWalking(actor, storage); } @@ -330,10 +323,19 @@ namespace MWMechanics } bool AiWander::getRepeat() const - { - return mRepeat; + { + return mRepeat; } + osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const + { + if (mHasDestination) + return mDestination; + + const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); + return currentPositionVec3f; + } bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) { @@ -342,35 +344,14 @@ namespace MWMechanics // End package if duration is complete if (mRemainingDuration <= 0) { - stopWalking(actor, storage); - return true; + stopWalking(actor, storage); + return true; } } // if get here, not yet completed return false; } - void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) - { - if (!mPathFinder.isPathConstructed()) - { - mDestination = mInitialActorPosition; - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); - - // actor position is already in world coordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); - - if (mPathFinder.isPathConstructed()) - { - storage.setState(Wander_Walking); - mHasDestination = true; - } - } - } - /* * Commands actor to walk to a random location near original spawn location. */ @@ -497,7 +478,7 @@ namespace MWMechanics } } - void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, + void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) { // Is there no destination or are we there yet? @@ -873,7 +854,7 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } @@ -914,7 +895,7 @@ namespace MWMechanics // get NPC's position in local (i.e. cell) coordinates osg::Vec3f npcPos(mInitialActorPosition); CoordinateConverter(cell).toLocal(npcPos); - + // Find closest pathgrid point int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); @@ -945,7 +926,7 @@ namespace MWMechanics storage.mPopulateAvailableNodes = false; } - // When only one path grid point in wander distance, + // 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. @@ -969,7 +950,7 @@ namespace MWMechanics 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; @@ -1041,4 +1022,3 @@ namespace MWMechanics init(); } } - diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6266a7708..d96d93165 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -47,9 +47,11 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); - + bool getRepeat() const; - + + osg::Vec3f getDestination(const MWWorld::Ptr& actor) const; + enum GreetingState { Greet_None, Greet_InProgress, @@ -85,7 +87,6 @@ namespace MWMechanics bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 676f01922..07e5fa7d6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -14,7 +14,7 @@ * * 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/ . + * https://www.gnu.org/licenses/ . */ #include "character.hpp" @@ -286,7 +286,7 @@ void CharacterController::refreshHitRecoilAnims() } else if (recovery) { - std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); + std::string anim = chooseRandomGroup("swimhit"); if (isSwimming && mAnimation->hasAnimation(anim)) { mHitState = CharState_SwimHit; @@ -517,8 +517,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) { - if(force || idle != mIdleState || - ((idle == mIdleState) && !mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) { mIdleState = idle; size_t numLoops = ~0ul; @@ -750,6 +749,7 @@ void CharacterController::playRandomDeath(float startpoint) CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) + , mWeapon(MWWorld::Ptr()) , mAnimation(anim) , mIdleState(CharState_None) , mMovementState(CharState_None) @@ -1157,17 +1157,26 @@ bool CharacterController::updateWeaponState() const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); - std::string soundid; + std::string upSoundId; + std::string downSoundId; if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) - { - soundid = (weaptype == WeapType_None) ? - weapon->getClass().getDownSoundId(*weapon) : - weapon->getClass().getUpSoundId(*weapon); - } + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + if(stats.getDrawState() == DrawState_Spell) + weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + + if(weapon != inv.end() && mWeaponType != WeapType_HandToHand && weaptype > WeapType_HandToHand && weaptype < WeapType_Spell) + upSoundId = weapon->getClass().getUpSoundId(*weapon); + + if(weapon != inv.end() && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell) + 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 == WeapType_HandToHand && mWeaponType != WeapType_Spell) + downSoundId = mWeapon.getClass().getDownSoundId(mWeapon); + + mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); } MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); @@ -1182,34 +1191,50 @@ bool CharacterController::updateWeaponState() if(weaptype != mWeaponType && !isKnockedOut() && !isKnockedDown() && !isRecovery()) { - forcestateupdate = true; - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - std::string weapgroup; - if(weaptype == WeapType_None) + if ((!isWerewolf || mWeaponType != WeapType_Spell) + && mUpperBodyState != UpperCharState_UnEquipingWeap + && !isStillWeapon) { - if ((!isWerewolf || mWeaponType != WeapType_Spell)) + // Note: we do not disable unequipping animation automatically to avoid body desync + getWeaponGroup(mWeaponType, weapgroup); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_UnEquipingWeap; + + if(!downSoundId.empty()) { - getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f); } } - else + + float complete; + bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if (!animPlaying || complete >= 1.0f) { + forcestateupdate = true; + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + getWeaponGroup(weaptype, weapgroup); mAnimation->setWeaponGroup(weapgroup); if (!isStillWeapon) { - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; + if (weaptype == WeapType_None) + { + // Disable current weapon animation manually + mAnimation->disable(mCurrentWeapon); + } + else + { + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; + } } if(isWerewolf) @@ -1222,16 +1247,16 @@ bool CharacterController::updateWeaponState() sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } } - } - if(!soundid.empty() && !isStillWeapon) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); - } + mWeaponType = weaptype; + getWeaponGroup(mWeaponType, mCurrentWeapon); - mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); + if(!upSoundId.empty() && !isStillWeapon) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); + } + } } if(isWerewolf) @@ -1752,8 +1777,7 @@ void CharacterController::update(float duration) if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) { - const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); - + const float encumbrance = cls.getNormalizedEncumbrance(mPtr); if (sneak) fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; else @@ -2152,7 +2176,7 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); } - else if(mode == 0) + else { mAnimQueue.resize(1); mAnimQueue.push_back(entry); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index cab51b82f..a172620b9 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -152,6 +152,7 @@ struct WeaponInfo; class CharacterController : public MWRender::Animation::TextKeyListener { MWWorld::Ptr mPtr; + MWWorld::Ptr mWeapon; MWRender::Animation *mAnimation; struct AnimationQueueEntry diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 13cd4232d..ac34c658b 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -1,6 +1,7 @@ #include "combat.hpp" #include +#include #include @@ -155,7 +156,11 @@ namespace MWMechanics if (!(weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) - damage *= multiplier; + { + if (weapon.getClass().getEnchantment(weapon).empty() + || !Settings::Manager::getBool("enchanted weapons are magical", "Game")) + damage *= multiplier; + } if ((weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver) && actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index a658c379a..08f30aba1 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -242,8 +242,11 @@ namespace MWMechanics return 0; if(mSoulGemPtr.getCellRef().getSoul()=="") return 0; - const ESM::Creature* soul = store.get().find(mSoulGemPtr.getCellRef().getSoul()); - return soul->mData.mSoul; + const ESM::Creature* soul = store.get().search(mSoulGemPtr.getCellRef().getSoul()); + if(soul) + return soul->mData.mSoul; + else + return 0; } int Enchanting::getMaxEnchantValue() const diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index bd5fa1b11..80b9ace86 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -22,6 +22,7 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include "aitravel.hpp" #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" @@ -79,21 +80,21 @@ namespace MWMechanics const ESM::NPC *player = ptr.get()->mBase; // reset - creatureStats.setLevel(player->mNpdt52.mLevel); + creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(); creatureStats.modifyMagicEffects(MagicEffects()); for (int i=0; i<27; ++i) - npcStats.getSkill (i).setBase (player->mNpdt52.mSkills[i]); - - creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt52.mStrength); - creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt52.mIntelligence); - creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt52.mWillpower); - creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt52.mAgility); - creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt52.mSpeed); - creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt52.mEndurance); - creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt52.mPersonality); - creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt52.mLuck); + 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(); @@ -629,22 +630,12 @@ namespace MWMechanics float d = static_cast(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); 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)); - - float x; - if(buying) x = buyTerm; - else x = std::min(buyTerm, sellTerm); - int offerPrice; - if (x < 1) - offerPrice = int(x * basePrice); - else - offerPrice = basePrice + int((x - 1) * basePrice); - offerPrice = std::max(1, offerPrice); - return offerPrice; + float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm))); + float sellTerm = 0.01f * std::min(75.f, (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 @@ -992,14 +983,26 @@ namespace MWMechanics } } - bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const std::string &ownerid) + 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)); - return ownerFound != owners.end(); + 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) @@ -1017,6 +1020,13 @@ namespace MWMechanics 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 @@ -1118,8 +1128,11 @@ namespace MWMechanics Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) - mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; - + { + const MWWorld::Ptr victimRef = MWBase::Environment::get().getWorld()->searchPtr(ownerCellRef->getOwner(), true); + if (victimRef.isEmpty() || !victimRef.getClass().getCreatureStats(victimRef).isDead()) + mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; + } if (alarm) commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } @@ -1191,16 +1204,41 @@ namespace MWMechanics reportCrime(player, victim, type, arg); else if (type == OT_Assault && !victim.isEmpty()) { + bool reported = false; if (victim.getClass().isClass(victim, "guard") && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) - reportCrime(player, victim, type, arg); - else + reported = reportCrime(player, victim, type, arg); + + if (!reported) startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? } return crimeSeen; } - void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) + 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(AiPackage::TypeIdFollow)) + { + if (playerFollowers.find(actor) != playerFollowers.end()) + return false; + } + + return true; + } + + bool MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -1275,29 +1313,15 @@ namespace MWMechanics bool reported = false; + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + // Tell everyone (including the original reporter) in alarm range for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { - if (*it == player - || !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue; - - if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) - continue; - - // Unconsious actor can not report about crime and should not become hostile - if (it->getClass().getCreatureStats(*it).getKnockedDown()) + if (!canReportCrime(*it, victim, playerFollowers)) continue; - // Player's followers should not attack player, or try to arrest him - if (it->getClass().getCreatureStats(*it).getAiSequence().hasPackage(AiPackage::TypeIdFollow)) - { - std::set playerFollowers; - getActorsSidingWith(player, playerFollowers); - - if (playerFollowers.find(*it) != playerFollowers.end()) - continue; - } - // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) { @@ -1306,8 +1330,14 @@ namespace MWMechanics if (type == OT_Trespassing) MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); } + } - if (it->getClass().isClass(*it, "guard")) + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if (!canReportCrime(*it, victim, playerFollowers)) + continue; + + if (it->getClass().isClass(*it, "guard") && reported) { // Mark as Alarmed for dialogue it->getClass().getCreatureStats(*it).setAlarmed(true); @@ -1395,6 +1425,8 @@ namespace MWMechanics victim.getClass().getNpcStats(victim).setCrimeId(id); } } + + return reported; } bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) @@ -1442,7 +1474,20 @@ namespace MWMechanics // 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(AiPackage::TypeIdPursue)) - startCombat(target, attacker); + { + // 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 == getPlayer()) + { + int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified()); + peaceful = (fight == 0); + } + + if (!peaceful) + startCombat(target, attacker); + } } return true; @@ -1544,9 +1589,12 @@ namespace MWMechanics void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { - if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) + MWMechanics::AiSequence& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + + if (aiSequence.isInCombat(target)) return; - ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); + + aiSequence.stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same @@ -1719,11 +1767,11 @@ namespace MWMechanics { if (werewolf) { - player->saveSkillsAttributes(); - player->setWerewolfSkillsAttributes(); + player->saveStats(); + player->setWerewolfStats(); } else - player->restoreSkillsAttributes(); + player->restoreStats(); } // Werewolfs can not cast spells, so we need to unset the prepared spell if there is one. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 4bf47cb16..07dfd81ff 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -205,7 +205,7 @@ namespace MWMechanics virtual std::vector > getStolenItemOwners(const std::string& itemid); /// Has the player stolen this item from the given owner? - virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); + virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr); virtual bool isBoundItem(const MWWorld::Ptr& item); @@ -224,7 +224,9 @@ namespace MWMechanics virtual bool isSneaking(const MWWorld::Ptr& ptr); private: - void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + 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, int arg=0); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index f0fc7fb6e..f15152759 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -214,7 +214,7 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, } } -void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress) +void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress, bool readBook) { int base = getSkill (skillIndex).getBase(); @@ -256,9 +256,14 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWindowManager()->playSound("skillraise"); std::stringstream message; + + if (readBook) + message << std::string("#{sBookSkillMessage}\n"); + message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") % static_cast (base); + MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), MWGui::ShowInDialogueMode_Never); if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 9ded47c07..d2e42e994 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -81,7 +81,7 @@ namespace MWMechanics 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); + void increaseSkill (int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook = false); int getLevelProgress() const; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 3c6f14bfd..0635a5520 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -26,13 +26,13 @@ namespace MWMechanics bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) { - if(getNearbyDoor(actor, minDist)!=MWWorld::Ptr()) - return true; - else + if(getNearbyDoor(actor, minDist).isEmpty()) return false; + else + return true; } - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) { MWWorld::CellStore *cell = actor.getCell(); @@ -50,6 +50,16 @@ namespace MWMechanics const MWWorld::LiveCellRef& ref = *it; osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); + + // FIXME: cast + const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); + + int doorState = doorPtr.getClass().getDoorState(doorPtr); + float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2]; + + if (doorState != 0 || doorRot != 0) + continue; // the door is already opened/opening + doorPos.z() = 0; float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length())); @@ -62,8 +72,7 @@ namespace MWMechanics if ((pos - doorPos).length2() > minDist*minDist) continue; - // FIXME cast - return MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); // found, stop searching + return doorPtr; // found, stop searching } return MWWorld::Ptr(); // none found diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index f71207346..6a84e0ef9 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -17,7 +17,7 @@ namespace MWMechanics /// Returns door pointer within range. No guarantee is given as to which one /** \return Pointer to the door, or NULL if none exists **/ - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); class ObstacleCheck { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1dccb6c9a..0591667b7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -316,7 +316,7 @@ namespace MWMechanics buildPath(startPoint, endPoint, cell, pathgridGraph); if (mPath.size() >= 2) { - // if 2nd waypoint of new path == 1st waypoint of old, + // if 2nd waypoint of new path == 1st waypoint of old, // delete 1st waypoint of new path. std::list::iterator iter = ++mPath.begin(); if (iter->mX == oldStart.mX diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c0122a861..ea4c973b7 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -8,7 +8,7 @@ namespace { - // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // 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 diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d864dc619..f6d92726d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -24,7 +25,6 @@ #include "../mwrender/animation.hpp" -#include "magiceffects.hpp" #include "npcstats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" @@ -221,48 +221,41 @@ namespace MWMechanics if (effects) magicEffects = effects; - float resisted = 0; - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - { - // Effects with no resistance attribute belonging to them can not be resisted - if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) - return 0.f; + // Effects with no resistance attribute belonging to them can not be resisted + if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) + return 0.f; - float resistance = getEffectResistanceAttribute(effectId, magicEffects); + float resistance = getEffectResistanceAttribute(effectId, magicEffects); - int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); - float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); - float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); + int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = static_cast(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 != NULL && !caster.isEmpty() && caster.getClass().isActor()) - { - castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance - } - if (castChance > 0) - x *= 50 / castChance; + // This makes spells that are easy to cast harder to resist and vice versa + float castChance = 100.f; + if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) + { + castChance = getSpellSuccessChance(spell, caster, NULL, 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; + float roll = Misc::Rng::rollClosedProbability() * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; - if (x <= roll) - x = 0; + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; 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); - - resisted = x; + x = roll / std::min(x, 100.f); } - return resisted; + x = std::min(x + resistance, 100.f); + return x; } float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, @@ -458,13 +451,12 @@ namespace MWMechanics } float magnitudeMult = 1; - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) - { - if (absorbed) - continue; - // Try reflecting - if (!reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) + if (!absorbed && target.getClass().isActor()) + { + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + // Reflect harmful effects + if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); bool isReflected = (Misc::Rng::roll0to99() < reflect); @@ -488,13 +480,17 @@ namespace MWMechanics else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } - - // If player is attempting to cast a harmful spell, show the target's HP bar - if (castByPlayer && target != caster) + else if (isHarmful && castByPlayer && target != caster) + { + // If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar MWBase::Environment::get().getWindowManager()->setEnemy(target); + } + + if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) + magnitudeMult = 0; // Notify the target actor they've been hit - if (target != caster && !caster.isEmpty()) + if (target != caster && !caster.isEmpty() && isHarmful) target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); } @@ -551,7 +547,7 @@ namespace MWMechanics || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) { - MWMechanics::AiFollow package(caster.getCellRef().getRefId(), true); + MWMechanics::AiFollow package(caster, true); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); } @@ -567,9 +563,12 @@ namespace MWMechanics ActiveSpells::ActiveEffect effect_ = effect; effect_.mMagnitude *= -1; absorbEffects.push_back(effect_); - // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + if (reflected && Settings::Manager::getBool("classic reflect absorb attribute behavior", "Game")) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true, + absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId()); + else + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); } } } @@ -609,7 +608,6 @@ namespace MWMechanics std::string texture = magicEffect->mParticle; - // TODO: VFX are no longer active after saving/reloading the game 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); @@ -877,20 +875,18 @@ namespace MWMechanics const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + stats.setFatigue(fatigue); bool fail = false; // Check success - if (!(mCaster == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())) + float successChance = getSpellSuccessChance(spell, mCaster); + if (Misc::Rng::roll0to99() >= successChance) { - float successChance = getSpellSuccessChance(spell, mCaster); - if (Misc::Rng::roll0to99() >= successChance) - { - if (mCaster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); - fail = true; - } + if (mCaster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; } if (fail) @@ -1111,8 +1107,6 @@ namespace MWMechanics bool receivedMagicDamage = false; - bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - switch (effectKey.mId) { case ESM::MagicEffect::DamageAttribute: @@ -1135,40 +1129,25 @@ namespace MWMechanics adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); break; case ESM::MagicEffect::DamageHealth: - if (!godmode) - { - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - } - + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); break; case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageFatigue: - if (!godmode) - { - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - } - + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); break; case ESM::MagicEffect::AbsorbHealth: - if (!godmode) - { - if (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - } + 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) - { - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - } - + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); break; case ESM::MagicEffect::DisintegrateArmor: @@ -1191,6 +1170,7 @@ namespace MWMechanics if (disintegrateSlot(actor, priorities[i], magnitude)) break; } + break; } case ESM::MagicEffect::DisintegrateWeapon: @@ -1213,12 +1193,9 @@ namespace MWMechanics if (weather > 1) damageScale *= fMagicSunBlockedMult; - if (!godmode) - { - adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); - if (magnitude * damageScale > 0.f) - receivedMagicDamage = true; - } + adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); + if (magnitude * damageScale > 0.f) + receivedMagicDamage = true; break; } @@ -1228,12 +1205,8 @@ namespace MWMechanics case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::Poison: { - if (!godmode) - { - adjustDynamicStat(creatureStats, 0, -magnitude); - receivedMagicDamage = true; - } - + adjustDynamicStat(creatureStats, 0, -magnitude); + receivedMagicDamage = true; break; } @@ -1314,4 +1287,24 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); } + void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); + + 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"); + + std::string texture = magicEffect->mParticle; + + bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); + if (anim && loop) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 1f5ef45bd..07c5b8477 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -6,6 +6,8 @@ #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -119,6 +121,21 @@ namespace MWMechanics bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; + class ApplyLoopingParticlesVisitor : public EffectSourceVisitor + { + private: + MWWorld::Ptr mActor; + + public: + ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) + : mActor(actor) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1); + }; } #endif diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index fad7bc96d..71c49f9df 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -59,7 +59,7 @@ namespace MWMechanics MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // Make the summoned creature follow its master and help in fights - AiFollow package(mActor.getCellRef().getRefId()); + AiFollow package(mActor); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index ede0d2de7..eee5e6449 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -32,7 +32,6 @@ namespace MWMechanics // Is the player buying? bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); int b = std::abs(playerOffer); int d = (buying) @@ -56,7 +55,7 @@ namespace MWMechanics float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float x = gmst.find("fBargainOfferMulti")->getFloat() * d + gmst.find("fBargainOfferBase")->getFloat() - + std::abs(int(pcTerm - npcTerm)); + + int(pcTerm - npcTerm); int roll = Misc::Rng::rollDice(100) + 1; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 174560e6b..e2fa7be54 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -309,6 +308,7 @@ namespace MWPhysics float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); ActorTracer tracer; + osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; @@ -321,10 +321,11 @@ namespace MWPhysics { velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; - if (velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) + 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 + physicActor->getInertialForce(); + else if (!physicActor->getOnGround() || physicActor->getOnSlope()) + velocity = velocity + inertia; } // dead actors underwater will float to the surface, if the CharacterController tells us to do so @@ -847,6 +848,16 @@ namespace MWPhysics 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; + RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, CollisionType_Actor, hitmask); + + if (result.mHit) + { + 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")->getFloat()/2.0f), queryDistance); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1bd839ead..3ccc06665 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -31,6 +31,8 @@ #include #include +#include + #include #include "../mwbase/environment.hpp" @@ -466,6 +468,8 @@ namespace MWRender mAnimationTimePtr[i].reset(new AnimationTime); mLightListCallback = new SceneUtil::LightListCallback; + + mUseAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); } Animation::~Animation() @@ -536,6 +540,35 @@ namespace MWRender 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; @@ -546,6 +579,14 @@ namespace MWRender else return; + addSingleAnimSource(kfname, baseModel); + + if (mUseAdditionalSources) + loadAllAnimationsInFolder(kfname, baseModel); + } + + void Animation::addSingleAnimSource(const std::string &kfname, const std::string& baseModel) + { if(!mResourceSystem->getVFS()->exists(kfname)) return; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index cff98a4b7..43a626899 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -275,6 +275,8 @@ protected: osg::ref_ptr mLightListCallback; + bool mUseAdditionalSources; + const NodeMap& getNodeMap() const; /* Sets the appropriate animations on the bone groups based on priority. @@ -309,12 +311,15 @@ protected: */ 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); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f7219959a..faaa3799e 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -1,5 +1,6 @@ #include "characterpreview.hpp" +#include #include #include @@ -13,6 +14,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -158,14 +160,25 @@ namespace MWRender stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; - lightmodel->setAmbientIntensity(osg::Vec4(0.25, 0.25, 0.25, 1.0)); + lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); - /// \todo Read the fallback values from INIImporter (Inventory:Directional*) ? osg::ref_ptr light = new osg::Light; - light->setPosition(osg::Vec4(-0.3,0.3,0.7, 0.0)); - light->setDiffuse(osg::Vec4(1,1,1,1)); - light->setAmbient(osg::Vec4(0,0,0,1)); + const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); + float diffuseR = fallback->getFallbackFloat("Inventory_DirectionalDiffuseR"); + float diffuseG = fallback->getFallbackFloat("Inventory_DirectionalDiffuseG"); + float diffuseB = fallback->getFallbackFloat("Inventory_DirectionalDiffuseB"); + float ambientR = fallback->getFallbackFloat("Inventory_DirectionalAmbientR"); + float ambientG = fallback->getFallbackFloat("Inventory_DirectionalAmbientG"); + float ambientB = fallback->getFallbackFloat("Inventory_DirectionalAmbientB"); + float azimuth = osg::DegreesToRadians(180.f - fallback->getFallbackFloat("Inventory_DirectionalRotationX")); + float altitude = osg::DegreesToRadians(fallback->getFallbackFloat("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)); + light->setAmbient(osg::Vec4(ambientR,ambientG,ambientB,1)); light->setSpecular(osg::Vec4(0,0,0,0)); light->setLightNum(0); light->setConstantAttenuation(1.f); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 24f6de6ce..af2bb101a 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -125,7 +125,7 @@ namespace MWRender { for (int cellX=0; cellX(float(cellX)/float(mCellSize) * 9); + int vertexX = static_cast(float(cellX) / float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); int texelX = (x-mMinX) * mCellSize + cellX; @@ -135,9 +135,9 @@ namespace MWRender float y2 = 0; if (land && (land->mDataTypes & ESM::Land::DATA_WNAM)) - y2 = (land->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + y2 = land->mWnam[vertexY * 9 + vertexX] / 128.f; else - y2 = (SCHAR_MIN << 4) / 2048.f; + y2 = SCHAR_MIN / 128.f; if (y2 < 0) { r = static_cast(14 * y2 + 38); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b78c4dcd2..0b65a6b13 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -174,6 +175,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setNodeMask(Mask_RenderToTexture); 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) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f0a3d2e38..db6772eaa 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -123,11 +123,12 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) mObjects.erase(iter); - if (ptr.getClass().isNpc()) + if (ptr.getClass().isActor()) { - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - store.setInvListener(NULL, ptr); - store.setContListener(NULL); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).setInvListener(NULL, ptr); + + ptr.getClass().getContainerStore(ptr).setContListener(NULL); } ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 295f33287..ff93e3d3d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -61,6 +61,16 @@ #include "terrainstorage.hpp" #include "util.hpp" +namespace +{ + float DLLandFogStart; + float DLLandFogEnd; + float DLUnderwaterFogStart; + float DLUnderwaterFogEnd; + float DLInteriorFogStart; + float DLInteriorFogEnd; +} + namespace MWRender { @@ -183,14 +193,18 @@ namespace MWRender , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) - , mFogDepth(0.f) + , mLandFogStart(0.f) + , mLandFogEnd(std::numeric_limits::max()) + , mUnderwaterFogStart(0.f) + , mUnderwaterFogEnd(std::numeric_limits::max()) , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) - , mUnderwaterFog(0.f) , mUnderwaterIndoorFog(fallback->getFallbackFloat("Water_UnderwaterIndoorFog")) , mNightEyeFactor(0.f) - , mFieldOfViewOverride(0.f) + , mDistantFog(false) + , mDistantTerrain(false) , mFieldOfViewOverridden(false) + , mFieldOfViewOverride(0.f) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); @@ -226,12 +240,20 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); - const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + 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"); + + mDistantFog = Settings::Manager::getBool("use distant fog", "Fog"); + mDistantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); mTerrainStorage = new TerrainStorage(mResourceSystem, Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"), + Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); - if (distantTerrain) + if (mDistantTerrain) mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); @@ -489,14 +511,44 @@ namespace MWRender { osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); - configureFog (cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, color); + 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 + configureFog(cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); } - void RenderingManager::configureFog(float fogDepth, float underwaterFog, const osg::Vec4f &color) + void RenderingManager::configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) { - mFogDepth = fogDepth; + 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 = mViewDistance * (1 - fogDepth); + mLandFogEnd = mViewDistance; + } + mUnderwaterFogStart = mViewDistance * (1 - underwaterFog); + mUnderwaterFogEnd = mViewDistance; + } mFogColor = color; - mUnderwaterFog = underwaterFog; } SkyManager* RenderingManager::getSkyManager() @@ -527,23 +579,15 @@ namespace MWRender float viewDistance = mViewDistance; viewDistance = std::min(viewDistance, 6666.f); setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); - mStateUpdater->setFogStart(viewDistance * (1 - mUnderwaterFog)); - mStateUpdater->setFogEnd(viewDistance); + mStateUpdater->setFogStart(mUnderwaterFogStart); + mStateUpdater->setFogEnd(mUnderwaterFogEnd); } else { setFogColor(mFogColor); - if (mFogDepth == 0.f) - { - mStateUpdater->setFogStart(0.f); - mStateUpdater->setFogEnd(std::numeric_limits::max()); - } - else - { - mStateUpdater->setFogStart(mViewDistance * (1 - mFogDepth)); - mStateUpdater->setFogEnd(mViewDistance); - } + mStateUpdater->setFogStart(mLandFogStart); + mStateUpdater->setFogEnd(mLandFogEnd); } } @@ -1159,7 +1203,8 @@ namespace MWRender else if (it->first == "Camera" && it->second == "viewing distance") { mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - mStateUpdater->setFogEnd(mViewDistance); + if(!mDistantFog) + mStateUpdater->setFogEnd(mViewDistance); updateProjectionMatrix(); } else if (it->first == "General" && (it->second == "texture filter" || diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8fd96cff9..6511036cf 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -107,7 +107,7 @@ namespace MWRender void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); - void configureFog(float fogDepth, float underwaterFog, const osg::Vec4f& colour); + 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); @@ -245,10 +245,12 @@ namespace MWRender osg::ref_ptr mStateUpdater; - float mFogDepth; + float mLandFogStart; + float mLandFogEnd; + float mUnderwaterFogStart; + float mUnderwaterFogEnd; osg::Vec4f mUnderwaterColor; float mUnderwaterWeight; - float mUnderwaterFog; float mUnderwaterIndoorFog; osg::Vec4f mFogColor; @@ -257,8 +259,10 @@ namespace MWRender float mNearClip; float mViewDistance; + bool mDistantFog : 1; + bool mDistantTerrain : 1; + bool mFieldOfViewOverridden : 1; float mFieldOfViewOverride; - bool mFieldOfViewOverridden; float mFieldOfView; float mFirstPersonFieldOfView; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 4357d468c..a9345cdb4 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -65,6 +65,9 @@ namespace MWRender float mFogDepth; + float mDLFogFactor; + float mDLFogOffset; + float mWindSpeed; float mCloudSpeed; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 36ff40279..3c617f794 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -513,13 +513,11 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) // 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. -#if !defined(OPENGL_ES) && !defined(ANDROID) Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager(); bool oldValue = sceneManager->getForceShaders(); sceneManager->setForceShaders(true); sceneManager->recreateShaders(node); sceneManager->setForceShaders(oldValue); -#endif } void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index d70ff5319..f4e729da1 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -406,7 +406,7 @@ namespace MWScript const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get().find(factionId); - if(rank < 0 || rank > 9) + if(rank < 0) return ""; return faction->mRanks[rank]; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 1067b5536..59f2cc9c6 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -454,7 +454,13 @@ namespace MWScript 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); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 68b1063e4..611199f72 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -530,7 +530,8 @@ namespace MWScript // create item MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, 1); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); + MWBase::Environment::get().getWorld()->scaleObject(ptr, actor.getCellRef().getScale()); } } }; @@ -578,26 +579,25 @@ namespace MWScript Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - const float *objRot = ptr.getRefData().getPosition().rot; + if (!ptr.getRefData().getBaseNode()) + return; - float ax = objRot[0]; - float ay = objRot[1]; - float az = objRot[2]; + // We can rotate actors only around Z axis + if (ptr.getClass().isActor() && (axis == "x" || axis == "y")) + return; + osg::Quat rot; if (axis == "x") - { - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); - } + rot = osg::Quat(rotation, -osg::X_AXIS); else if (axis == "y") - { - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); - } + rot = osg::Quat(rotation, -osg::Y_AXIS); else if (axis == "z") - { - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); - } + rot = osg::Quat(rotation, -osg::Z_AXIS); else throw std::runtime_error ("invalid rotation axis: " + axis); + + osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude(); + MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot); } }; diff --git a/apps/openmw/mwsound/alext.h b/apps/openmw/mwsound/alext.h index 4b9a15537..7162fa955 100644 --- a/apps/openmw/mwsound/alext.h +++ b/apps/openmw/mwsound/alext.h @@ -15,7 +15,7 @@ * 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 http://www.gnu.org/copyleft/lgpl.html + * Or go to https://www.gnu.org/copyleft/lgpl.html */ #ifndef AL_ALEXT_H diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index e06a68d64..2b76e0d7d 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -21,7 +21,7 @@ extern "C" // From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: // https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d -// http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 +// https://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 #include } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 829c001e5..31d46ce31 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -951,10 +951,11 @@ std::pair OpenAL_Output::loadSound(const std::string &fname getALError(); std::vector data; - ALenum format; - int srate; + ALenum format = AL_NONE; + int srate = 0; - try { + 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)) @@ -974,7 +975,8 @@ std::pair OpenAL_Output::loadSound(const std::string &fname format = getALFormat(chans, type); if(format) decoder->readAll(data); } - catch(std::exception &e) { + catch(std::exception &e) + { std::cerr<< "Failed to load audio from "< filelist; + const std::vector &filelist = mMusicFiles[mCurrentPlaylist]; auto &tracklist = mMusicToPlay[mCurrentPlaylist]; - if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end()) - { - const std::map& index = mVFS->getIndex(); - - std::string pattern = "Music/" + mCurrentPlaylist; - 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[mCurrentPlaylist] = filelist; - } - else - filelist = mMusicFiles[mCurrentPlaylist]; - - if(filelist.empty()) - return; // Do a Fisher-Yates shuffle @@ -454,6 +430,33 @@ namespace MWSound 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(); } @@ -574,6 +577,9 @@ namespace MWSound Sound_Buffer *sfx = loadSound(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()); + Sound *sound = getSoundRef(); sound->init(volume * sfx->mVolume, volumeFromType(type), pitch, mode|type|Play_2D); if(!mOutput->playSound(sound, sfx->mHandle, offset)) @@ -608,7 +614,7 @@ namespace MWSound return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy - stopSound3D(ptr, soundId); + stopSound(sfx, ptr); bool played; Sound *sound = getSoundRef(); @@ -675,12 +681,11 @@ namespace MWSound mOutput->finishSound(sound); } - void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId) + void SoundManager::stopSound(Sound_Buffer *sfx, const MWWorld::ConstPtr &ptr) { SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); for(SoundBufferRefPair &snd : snditer->second) { if(snd.second == sfx) @@ -689,6 +694,22 @@ namespace MWSound } } + void SoundManager::stopSound(const std::string& soundId) + { + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + if (!sfx) return; + + stopSound(sfx, MWWorld::ConstPtr()); + } + + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId) + { + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + if (!sfx) return; + + stopSound(sfx, ptr); + } + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr) { SoundMap::iterator snditer = mActiveSounds.find(ptr); @@ -712,6 +733,7 @@ namespace MWSound mOutput->finishSound(sndbuf.first); } } + for(SaySoundMap::value_type &snd : mActiveSaySounds) { if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) @@ -719,20 +741,6 @@ namespace MWSound } } - void SoundManager::stopSound(const std::string& soundId) - { - SoundMap::iterator snditer = mActiveSounds.find(MWWorld::ConstPtr()); - if(snditer != mActiveSounds.end()) - { - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); - for(SoundBufferRefPair &sndbuf : snditer->second) - { - if(sndbuf.second == sfx) - mOutput->finishSound(sndbuf.first); - } - } - } - void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, float duration) { diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index e31a0e575..4064a05af 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -145,6 +145,9 @@ namespace MWSound 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, const std::map& fallbackMap, bool useSound); virtual ~SoundManager(); diff --git a/apps/openmw/mwstate/quicksavemanager.cpp b/apps/openmw/mwstate/quicksavemanager.cpp new file mode 100644 index 000000000..59658ce6e --- /dev/null +++ b/apps/openmw/mwstate/quicksavemanager.cpp @@ -0,0 +1,38 @@ +#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) +{ + if(mOldestSlotVisited == NULL) + return true; + return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); +} + +bool MWState::QuickSaveManager::shouldCreateNewSlot() +{ + return (mSlotsVisited < mMaxSaves); +} + +const MWState::Slot *MWState::QuickSaveManager::getNextQuickSaveSlot() +{ + if(shouldCreateNewSlot()) + return NULL; + return mOldestSlotVisited; +} diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp new file mode 100644 index 000000000..e52cd609f --- /dev/null +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -0,0 +1,35 @@ +#ifndef GAME_STATE_QUICKSAVEMANAGER_H +#define GAME_STATE_QUICKSAVEMANAGER_H + +#include + +#include "character.hpp" +#include "../mwbase/statemanager.hpp" + +namespace MWState{ + class QuickSaveManager{ + std::string mSaveName; + unsigned int mMaxSaves; + unsigned int mSlotsVisited; + const Slot *mOldestSlotVisited; + private: + bool shouldCreateNewSlot(); + bool isOldestSave(const Slot *compare); + 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 NULL if a new slot can be made + }; +} + +#endif diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 14ee5adee..c1bb589e8 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -37,6 +37,8 @@ #include "../mwscript/globalscripts.hpp" +#include "quicksavemanager.hpp" + void MWState::StateManager::cleanup (bool force) { if (mState!=State_NoGame || force) @@ -324,20 +326,25 @@ void MWState::StateManager::quickSave (std::string name) return; } - const Slot* slot = NULL; + int maxSaves = Settings::Manager::getInt("max quicksaves", "Saves"); + if(maxSaves < 1) + maxSaves = 1; + Character* currentCharacter = getCurrentCharacter(); //Get current character + QuickSaveManager saveFinder = QuickSaveManager(name, maxSaves); - //Find quicksave slot if (currentCharacter) { for (Character::SlotIterator it = currentCharacter->begin(); it != currentCharacter->end(); ++it) { - if (it->mProfile.mDescription == name) - slot = &*it; + //Visiting slots allows the quicksave finder to find the oldest quicksave + saveFinder.visitSave(&*it); } } - saveGame(name, slot); + //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) diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index d2dc972fc..58611f0af 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -52,7 +52,7 @@ namespace MWWorld playerRef->mBase->mClass ); - npcStats.increaseSkill (ref->mBase->mData.mSkillId, *class_, true); + npcStats.increaseSkill (ref->mBase->mData.mSkillId, *class_, true, true); npcStats.flagAsUsed (ref->mBase->mId); } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index f33c7bb67..1b6495c11 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -677,7 +677,6 @@ namespace MWWorld if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) mWaterLevel = state.mWaterLevel; - mWaterLevel = state.mWaterLevel; mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } @@ -688,7 +687,6 @@ namespace MWWorld if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) state.mWaterLevel = mWaterLevel; - state.mWaterLevel = mWaterLevel; state.mHasFogOfWar = (mFogState.get() ? 1 : 0); state.mLastRespawn = mLastRespawn.toEsm(); } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index e59dde7b1..5425c2bd3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -462,10 +462,15 @@ namespace MWWorld 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 getEncumbrance(ptr) / capacity; + return encumbrance / capacity; } std::string Class::getSound(const MWWorld::ConstPtr&) const diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 981f63e34..6c369a154 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -47,7 +47,7 @@ namespace for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2)) + if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) { MWWorld::Ptr ptr (&*iter, 0); ptr.setContainerStore (store); @@ -256,11 +256,7 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& 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); - // a bit pointless to set owner for the player - if (actorPtr != MWMechanics::getPlayer()) - return add(ref.getPtr(), count, actorPtr, true); - else - return add(ref.getPtr(), count, actorPtr, false); + return add(ref.getPtr(), count, actorPtr, true); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner) @@ -679,6 +675,30 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) "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 (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) + { + 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) { { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index dbb82cbda..b67eb6552 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -198,6 +198,9 @@ namespace MWWorld ///< 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; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 4a1763d0a..2fe9ebcad 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -141,6 +141,7 @@ void ESMStore::setUp() mMagicEffects.setUp(); mAttributes.setUp(); mDialogs.setUp(); + mStatics.setUp(); } int ESMStore::countSavedGameRecords() const diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 294734f46..b8178f774 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -48,6 +48,12 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) } mClass->readAdditionalState (ptr, state); + + if (!mRef.getSoul().empty() && !MWBase::Environment::get().getWorld()->getStore().get().search(mRef.getSoul())) + { + std::cerr << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem" << std::endl; + mRef.setSoul(std::string()); + } } void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 19bf7f55e..5439447fd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -47,37 +47,45 @@ namespace MWWorld mPlayer.mData.setPosition(playerPos); } - void Player::saveSkillsAttributes() + 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")->getFloat())); for (int i=0; i& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); + 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")->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 = stats.getAttribute(i); + MWMechanics::AttributeValue value = npcStats.getAttribute(i); value.setBase(int(gmst.find(name)->getFloat())); - stats.setAttribute(i, value); + npcStats.setAttribute(i, value); } for(size_t i = 0;i < ESM::Skill::Length;i++) @@ -90,9 +98,9 @@ namespace MWWorld std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : ESM::Skill::sSkillNames[i]); - MWMechanics::SkillValue value = stats.getSkill(i); + MWMechanics::SkillValue value = npcStats.getSkill(i); value.setBase(int(gmst.find(name)->getFloat())); - stats.setSkill(i, value); + npcStats.setSkill(i, value); } } @@ -279,6 +287,7 @@ namespace MWWorld mAttackingOrSpell = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; + mPreviousItems.clear(); mLastKnownExteriorPosition = osg::Vec3f(0,0,0); for (int i=0; i + #include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" @@ -46,7 +48,10 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) - // Saved skills and attributes prior to becoming a werewolf + 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]; @@ -56,9 +61,9 @@ namespace MWWorld Player(const ESM::NPC *player); - void saveSkillsAttributes(); - void restoreSkillsAttributes(); - void setWerewolfSkillsAttributes(); + void saveStats(); + void restoreStats(); + void setWerewolfStats(); // For mark/recall magic effects void markPosition (CellStore* markedCell, const ESM::Position& markedPosition); @@ -120,6 +125,10 @@ namespace MWWorld 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); }; } #endif diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0a27cf257..2c2d401d1 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -79,6 +79,9 @@ namespace if (ptr.getClass().isActor()) rendering.addWaterRippleEmitter(ptr); + + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, @@ -457,8 +460,7 @@ namespace MWWorld float z = pos.rot[2]; world->rotateObject(player, x, y, z); - if (adjustPlayerPos) - player.getClass().adjustPosition(player, true); + player.getClass().adjustPosition(player, true); } MWBase::MechanicsManager *mechMgr = diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 9279e3fe7..6f0a1b49f 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -592,7 +592,7 @@ namespace MWWorld const ESM::Cell *ptr = search(id); if (ptr == 0) { std::ostringstream msg; - msg << "Interior cell '" << id << "' not found"; + msg << "Cell '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; @@ -1053,6 +1053,32 @@ namespace MWWorld } } + template<> + void Store::setUp() + { + // Load default marker definitions, if game files do not have them for some reason + std::pair markers[] = { + 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") + }; + + for (const std::pair marker : markers) + { + if (search(marker.first) == 0) + { + ESM::Static newMarker = ESM::Static(marker.first, marker.second); + std::pair ret = mStatic.insert(std::make_pair(marker.first, newMarker)); + if (ret.first != mStatic.end()) + { + mShared.push_back(&ret.first->second); + } + } + } + } + template <> inline RecordId Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 2f0a2f8cf..d12e633be 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,10 +6,8 @@ #include #include #include -#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -44,49 +42,67 @@ namespace } template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const +T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const { - // TODO: use pre/post sunset/sunrise time values in [Weather] section + 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 || gameHour >= timeSettings.mNightStart + 1) + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) return mNightValue; // sunrise - else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) + else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { - if (gameHour <= timeSettings.mSunriseTime) + float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; + float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = timeSettings.mSunriseTime - gameHour; - float factor = advance / 0.5f; + 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 - timeSettings.mSunriseTime; - float factor = advance / 3.f; + 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 + 1 && gameHour <= timeSettings.mDayEnd - 1) + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) return mDayValue; // sunset - else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) + else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) { - if (gameHour <= timeSettings.mDayEnd + 1) + float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; + float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = (timeSettings.mDayEnd + 1) - gameHour; - float factor = (advance / 2); + 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 - (timeSettings.mDayEnd + 1); - float factor = advance / 2.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mNightValue, factor); } } @@ -103,6 +119,8 @@ Weather::Weather(const std::string& name, const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, + float dlFactor, + float dlOffset, const std::string& particleEffect) : mCloudTexture(fallback.getFallbackString("Weather_" + name + "_Cloud_Texture")) , mSkyColor(fallback.getFallbackColour("Weather_" + name +"_Sky_Sunrise_Color"), @@ -142,6 +160,8 @@ Weather::Weather(const std::string& name, , mFlashDecrement(fallback.getFallbackFloat("Weather_" + name + "_Flash_Decrement")) , mFlashBrightness(0.0f) { + mDL.FogFactor = dlFactor; + mDL.FogOffset = dlOffset; mThunderSoundID[0] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_0"); mThunderSoundID[1] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_1"); mThunderSoundID[2] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_2"); @@ -520,6 +540,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mSecunda("Secunda", fallback) , mWindSpeed(0.f) , mIsStorm(false) + , mPrecipitation(false) , mStormDirection(0,1,0) , mCurrentRegion() , mTimePassed(0) @@ -535,22 +556,42 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mNightEnd = mSunriseTime; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; mTimeSettings.mDayEnd = mSunsetTime; - mTimeSettings.mSunriseTime = mSunriseTime; + + mTimeSettings.addSetting(fallback, "Sky"); + mTimeSettings.addSetting(fallback, "Ambient"); + mTimeSettings.addSetting(fallback, "Fog"); + mTimeSettings.addSetting(fallback, "Sun"); + + // Morrowind handles stars settings differently for other ones + mTimeSettings.mStarsPostSunsetStart = fallback.getFallbackFloat("Weather_Stars_Post-Sunset_Start"); + mTimeSettings.mStarsPreSunriseFinish = fallback.getFallbackFloat("Weather_Stars_Pre-Sunrise_Finish"); + mTimeSettings.mStarsFadingDuration = fallback.getFallbackFloat("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); - addWeather("Clear", fallback); // 0 - addWeather("Cloudy", fallback); // 1 - addWeather("Foggy", fallback); // 2 - addWeather("Overcast", fallback); // 3 - addWeather("Rain", fallback); // 4 - addWeather("Thunderstorm", fallback); // 5 - addWeather("Ashstorm", fallback, "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", fallback, "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", fallback, "meshes\\snow.nif"); // 8 - addWeather("Blizzard", fallback, "meshes\\blizzard.nif"); // 9 + // These distant land fog factor and offset values are the defaults MGE XE provides. Should be + // provided by settings somewhere? + addWeather("Clear", fallback, 1.0f, 0.0f); // 0 + addWeather("Cloudy", fallback, 0.9f, 0.0f); // 1 + addWeather("Foggy", fallback, 0.2f, 30.0f); // 2 + addWeather("Overcast", fallback, 0.7f, 0.0f); // 3 + addWeather("Rain", fallback, 0.5f, 10.0f); // 4 + addWeather("Thunderstorm", fallback, 0.5f, 20.0f); // 5 + addWeather("Ashstorm", fallback, 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", fallback, 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", fallback, 0.5f, 40.0f, "meshes\\snow.nif"); // 8 + addWeather("Blizzard", fallback, 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 Store::iterator it = store.get().begin(); for(; it != store.get().end(); ++it) @@ -608,14 +649,11 @@ void WeatherManager::modRegion(const std::string& regionID, const std::vectorisCellExterior() || world->isCellQuasiExterior()) { - std::string playerRegion = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion); std::map::iterator it = mRegions.find(playerRegion); if(it != mRegions.end() && playerRegion != mCurrentRegion) { @@ -625,11 +663,9 @@ void WeatherManager::playerTeleported() } } -void WeatherManager::update(float duration, bool paused) +void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); - MWBase::World& world = *MWBase::Environment::get().getWorld(); - TimeStamp time = world.getTimeStamp(); if(!paused || mFastForward) { @@ -647,8 +683,7 @@ void WeatherManager::update(float duration, bool paused) updateWeatherTransitions(duration); } - const bool exterior = (world.isCellExterior() || world.isCellQuasiExterior()); - if(!exterior) + if(!isExterior) { mRendering.setSkyEnabled(false); stopSounds(); @@ -660,6 +695,10 @@ void WeatherManager::update(float duration, bool paused) mWindSpeed = mResult.mWindSpeed; 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 playerPos (player.getRefData().getPosition().asVec3()); @@ -694,10 +733,13 @@ void WeatherManager::update(float duration, bool paused) const float nightDuration = 24.f - dayDuration; double theta; - if ( !is_night ) { + if ( !is_night ) + { theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = static_cast(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); + } + else + { + theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -707,7 +749,7 @@ void WeatherManager::update(float duration, bool paused) mRendering.setSunDirection( final * -1 ); } - float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) @@ -720,7 +762,8 @@ void WeatherManager::update(float duration, bool paused) mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); - mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mFogColor); + 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); @@ -777,12 +820,11 @@ unsigned int WeatherManager::getWeatherID() const return mCurrentWeather; } -bool WeatherManager::isDark() const +bool WeatherManager::useTorches(float hour) const { - TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); - bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() - || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); - return exterior && (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart - 1); + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; + + return isDark && !mPrecipitation; } void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) @@ -866,11 +908,12 @@ void WeatherManager::clear() inline void WeatherManager::addWeather(const std::string& name, const Fallback::Map& fallback, + float dlFactor, float dlOffset, const std::string& particleEffect) { static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); - Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, particleEffect); + Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); mWeatherSettings.push_back(weather); } @@ -1055,18 +1098,25 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); - mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); - mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings); - mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings); - mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings); - mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings); - mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings); + 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; - if (gameHour >= mSunsetTime - mSunPreSunsetTime) + WeatherSetting setting = mTimeSettings.getSetting("Sun"); + float preSunsetTime = setting.mPreSunsetTime; + + if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) { - float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + 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 @@ -1080,15 +1130,17 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam else mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); - if (gameHour >= mSunsetTime) + if (gameHour >= mTimeSettings.mDayEnd) { - float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + // 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 >= mSunriseTime && gameHour <= mSunriseTime + 1) + else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) { - mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + // sunrise + mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; } else mResult.mSunDiscColor.a() = 1; @@ -1113,6 +1165,8 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const 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.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); @@ -1125,7 +1179,6 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mParticleEffect = current.mParticleEffect; mResult.mRainSpeed = current.mRainSpeed; mResult.mRainFrequency = current.mRainFrequency; mResult.mAmbientSoundVolume = 1-(factor*2); @@ -1137,7 +1190,6 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mIsStorm = other.mIsStorm; mResult.mParticleEffect = other.mParticleEffect; mResult.mRainEffect = other.mRainEffect; - mResult.mParticleEffect = other.mParticleEffect; mResult.mRainSpeed = other.mRainSpeed; mResult.mRainFrequency = other.mRainFrequency; mResult.mAmbientSoundVolume = 2*(factor-0.5f); diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 84a6c5105..cf6868356 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwbase/soundmanager.hpp" #include "../mwrender/sky.hpp" @@ -38,6 +40,13 @@ namespace MWWorld { class TimeStamp; + struct WeatherSetting + { + float mPreSunriseTime; + float mPostSunriseTime; + float mPreSunsetTime; + float mPostSunsetTime; + }; struct TimeOfDaySettings { @@ -45,7 +54,37 @@ namespace MWWorld float mNightEnd; float mDayStart; float mDayEnd; - float mSunriseTime; + + 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 Fallback::Map& fallback, const std::string& type) + { + WeatherSetting setting = { + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunset_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunset_Time") + }; + + mSunriseTransitions[type] = setting; + } }; /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. @@ -59,7 +98,7 @@ namespace MWWorld { } - T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; + T getValue (const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const; private: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; @@ -73,6 +112,8 @@ namespace MWWorld const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, + float dlFactor, + float dlOffset, const std::string& particleEffect); std::string mCloudTexture; @@ -102,6 +143,12 @@ namespace MWWorld // 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; @@ -218,14 +265,14 @@ namespace MWWorld */ void changeWeather(const std::string& regionID, const unsigned int weatherID); void modRegion(const std::string& regionID, const std::vector& chances); - void playerTeleported(); + void playerTeleported(const std::string& playerRegion, bool isExterior); /** * Per-frame update * @param duration * @param paused */ - void update(float duration, bool paused = false); + void update(float duration, bool paused, const TimeStamp& time, bool isExterior); void stopSounds(); @@ -240,8 +287,7 @@ namespace MWWorld unsigned int getWeatherID() const; - /// @see World::isDark - bool isDark() const; + bool useTorches(float hour) const; void write(ESM::ESMWriter& writer, Loading::Listener& progress); @@ -275,6 +321,7 @@ namespace MWWorld float mWindSpeed; bool mIsStorm; + bool mPrecipitation; osg::Vec3f mStormDirection; std::string mCurrentRegion; @@ -293,6 +340,7 @@ namespace MWWorld void addWeather(const std::string& name, const Fallback::Map& fallback, + float dlFactor, float dlOffset, const std::string& particleEffect = ""); void importRegions(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index def9595c0..000bdfa1a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -406,7 +406,6 @@ namespace MWWorld gmst["sCompanionWarningMessage"] = ESM::Variant("Warning message"); gmst["sCompanionWarningButtonOne"] = ESM::Variant("Button 1"); gmst["sCompanionWarningButtonTwo"] = ESM::Variant("Button 2"); - gmst["sCompanionShare"] = ESM::Variant("Companion Share"); gmst["sProfitValue"] = ESM::Variant("Profit Value"); gmst["sTeleportDisabled"] = ESM::Variant("Teleport disabled"); gmst["sLevitateDisabled"] = ESM::Variant("Levitate disabled"); @@ -1074,20 +1073,29 @@ namespace MWWorld 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 pos = ptr.getRefData().getPosition().asVec3(); + 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()) - pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera - else { - // 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 - osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); - pos.z() += halfExtents.z() * 2 * 0.75; - distance += halfExtents.y(); + 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()); @@ -1343,6 +1351,15 @@ namespace MWWorld rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } + void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate) + { + if(ptr.getRefData().getBaseNode() != 0) + { + mRendering->rotateObject(ptr, rotate); + mPhysics->updateRotation(ptr); + } + } + MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); @@ -1622,7 +1639,7 @@ namespace MWWorld if (!paused) doPhysics (duration); - updatePlayer(paused); + updatePlayer(); mPhysics->debugDraw(); @@ -1638,7 +1655,7 @@ namespace MWWorld } } - void World::updatePlayer(bool paused) + void World::updatePlayer() { MWWorld::Ptr player = getPlayerPtr(); @@ -1671,7 +1688,7 @@ namespace MWWorld bool swimming = isSwimming(player); static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->getFloat(); - if(!paused && sneaking && !(swimming || inair)) + if (sneaking && !(swimming || inair)) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else mRendering->getCamera()->setSneakOffset(0.f); @@ -2244,6 +2261,8 @@ namespace MWWorld model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); mPhysics->remove(getPlayerPtr()); mPhysics->addActor(getPlayerPtr(), model); + + applyLoopingParticles(player); } int World::canRest () @@ -2605,11 +2624,11 @@ namespace MWWorld int y = std::stoi(name.substr(name.find(',')+1)); ext = getExterior(x, y)->getCell(); } - catch (std::invalid_argument) + 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 (std::out_of_range) + catch (const std::out_of_range&) { throw std::runtime_error("Cell coordinates out of range."); } @@ -2747,64 +2766,66 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // Get the target to use for "on touch" effects, using the facing direction from Head node - MWWorld::Ptr target; - float distance = getActivationDistancePlusTelekinesis(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - 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); - osg::Vec3f dest = origin + direction * distance; - // 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()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); - // For actor targets, we want to use bounding boxes (physics raycast). - // 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. + const float fCombatDistance = getStore().get().find("fCombatDistance")->getFloat(); - MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, targetActors, MWPhysics::CollisionType_Actor); - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); + // for player we can take faced object first + MWWorld::Ptr target; + if (actor == MWMechanics::getPlayer()) + target = getFacedObject(); - if (result1.mHit) - dist1 = (origin - result1.mHitPos).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); + // if the faced object can not be activated, do not use it + if (!target.isEmpty() && !target.getClass().canBeActivated(target)) + target = NULL; - if (result1.mHit) - { - target = result1.mHitObject; - hitPosition = result1.mHitPos; - if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target))) - target = NULL; - } - else if (result2.mHit) + if (target.isEmpty()) { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) - target = NULL; - } + // 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); - // When targeting an actor that is in combat with an "on touch" spell, - // compare against the minimum of activation distance and combat distance. + // Get the target to use for "on touch" effects, using the facing direction from Head node + osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) - { - distance = std::min (distance, getStore().get().find("fCombatDistance")->getFloat()); - if (distance < dist1) - target = NULL; + 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 = NULL; + } + else if (result2.mHit) + { + target = result2.mHitObject; + hitPosition = result2.mHitPointWorld; + if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) + target = NULL; + } } std::string selectedSpell = stats.getSpells().getSelectedSpell(); @@ -2836,6 +2857,19 @@ namespace MWWorld mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } + void World::applyLoopingParticles(const MWWorld::Ptr& ptr) + { + const MWWorld::Class &cls = ptr.getClass(); + if (cls.isActor()) + { + MWMechanics::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; @@ -2852,11 +2886,17 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); } - bool World::isDark() const + 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()) - return mWeatherManager->isDark(); + { + float hour = getTimeStamp().getHour(); + return mWeatherManager->useTorches(hour); + } else { uint32_t ambient = cell->getCell()->mAmbi.mAmbient; @@ -3017,13 +3057,17 @@ namespace MWWorld void World::updateWeather(float duration, bool paused) { + bool isExterior = isCellExterior() || isCellQuasiExterior(); if (mPlayer->wasTeleported()) { mPlayer->setTeleported(false); - mWeatherManager->playerTeleported(); + + const std::string playerRegion = Misc::StringUtils::lowerCase(getPlayerPtr().getCell()->getCell()->mRegion); + mWeatherManager->playerTeleported(playerRegion, isExterior); } - mWeatherManager->update(duration, paused); + const TimeStamp time = getTimeStamp(); + mWeatherManager->update(duration, paused, time, isExterior); } struct AddDetectedReferenceVisitor @@ -3438,7 +3482,7 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - weaponPos.z() += mPhysics->getHalfExtents(actor).z() * 2 * 0.75; + weaponPos.z() += mPhysics->getHalfExtents(actor).z(); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } @@ -3447,7 +3491,7 @@ namespace MWWorld { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); - weaponPos.z() += halfExtents.z() * 2 * 0.75; + weaponPos.z() += halfExtents.z(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 17e103290..120397d28 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -129,7 +129,7 @@ namespace MWWorld Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); - void updatePlayer(bool paused); + void updatePlayer(); void preloadSpells(); @@ -214,6 +214,8 @@ namespace MWWorld void setWaterHeight(const float height) override; + void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) override; + bool toggleWater() override; bool toggleWorld() override; @@ -605,12 +607,13 @@ namespace MWWorld void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; + void applyLoopingParticles(const MWWorld::Ptr& ptr); const std::vector& getContentFiles() const override; - void breakInvisibility (const MWWorld::Ptr& actor) override; - // Are we in an exterior or pseudo-exterior cell and it's night? - bool isDark() const override; + + // Allow NPCs to use torches? + bool useTorches() const override; bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 9b09bc41f..48c8be4d8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,7 +1,7 @@ find_package(GTest REQUIRED) if (GTEST_FOUND) - include_directories(${GTEST_INCLUDE_DIRS}) + include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 59dd7fe66..598e8daad 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -66,10 +66,8 @@ TEST(EsmFixedString, struct_size) TEST(EsmFixedString, DISABLED_is_pod) { - /* TODO: enable in C++11 - * ASSERT_TRUE(std::is_pod::value); - * ASSERT_TRUE(std::is_pod::value); - * ASSERT_TRUE(std::is_pod::value); - * ASSERT_TRUE(std::is_pod::value); - */ + ASSERT_TRUE(std::is_pod::value); + ASSERT_TRUE(std::is_pod::value); + ASSERT_TRUE(std::is_pod::value); + ASSERT_TRUE(std::is_pod::value); } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 5f7338e52..8d97bbcbf 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -118,7 +118,7 @@ if (DESIRED_QT_VERSION MATCHES 4) target_link_libraries(openmw-wizard ${QT_QTMAIN_LIBRARY}) endif() else() - qt5_use_modules(openmw-wizard Widgets Core) + target_link_libraries(openmw-wizard Qt5::Widgets Qt5::Core) endif() if (OPENMW_USE_UNSHIELD) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 7a4dcbf10..3deb30f25 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -161,7 +161,7 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) if (path.isEmpty()) { logTextEdit->appendHtml(tr("


        \ - Error: The installation was aborted by the user

        ")); + Error: The installation was aborted by the user

        ")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index b99f151aa..57d080cf8 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -62,10 +62,11 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setupInstallations(); setupPages(); - const boost::filesystem::path& installedPath = mCfgMgr.getInstallPath(); - if (!installedPath.empty()) + const boost::filesystem::path& installationPath = mCfgMgr.getInstallPath(); + if (!installationPath.empty()) { - addInstallation(toQString(installedPath)); + const boost::filesystem::path& dataPath = installationPath / "Data Files"; + addInstallation(toQString(dataPath)); } } @@ -230,29 +231,13 @@ void Wizard::MainWizard::setupInstallations() void Wizard::MainWizard::runSettingsImporter() { + writeSettings(); + QString path(field(QLatin1String("installation.path")).toString()); - // Create the file if it doesn't already exist, else the importer will fail QString userPath(toQString(mCfgMgr.getUserConfigPath())); QFile file(userPath + QLatin1String("openmw.cfg")); - 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 qApp->quit(); - } - - file.close(); - } - // Construct the arguments to run the importer QStringList arguments; diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 020de9f80..9cdb4cd78 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -472,8 +472,8 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) if (morrowindFound) { // Check if we have correct archive, other archives have Morrowind.bsa too - if ((tribunalFound && bloodmoonFound) - || (!tribunalFound && !bloodmoonFound)) { + if (tribunalFound == bloodmoonFound) + { cabFile = file; found = true; // We have a GoTY disk or a Morrowind-only disk } diff --git a/appveyor.yml b/appveyor.yml index d8f2bfc35..97eaa0e26 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,35 +8,32 @@ branches: environment: matrix: - - msvc: 2013 - msvc: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - msvc: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 platform: - - Win32 -# - x64 +# - Win32 + - x64 configuration: - - Debug -# - Release +# - Debug + - Release +# - RelWithDebInfo # For the Qt, Boost, CMake, etc installs -os: Visual Studio 2015 +#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.83.7-msvc2013-win32.7z - - C:\projects\openmw\deps\Bullet-2.83.7-msvc2013-win64.7z - C:\projects\openmw\deps\Bullet-2.83.7-msvc2015-win32.7z - C:\projects\openmw\deps\Bullet-2.83.7-msvc2015-win64.7z - - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2013-win32.7z - - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2013-win32.7z - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2015-win64.7z - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2015-win64.7z - - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2013-win32.7z - - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2013-win32.7z - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2015-win64.7z - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2015-win64.7z - C:\projects\openmw\deps\ffmpeg-3.0.1-dev-win32.7z @@ -52,13 +49,17 @@ install: - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -p %PLATFORM% -v %msvc% + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V 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" +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%\MSVC%msvc%_64\%configuration%\ -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: @@ -67,3 +68,9 @@ test: off # - # 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 diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake index 3d28613ae..3b7586835 100644 --- a/cmake/FindFreetype.cmake +++ b/cmake/FindFreetype.cmake @@ -1,7 +1,7 @@ #------------------------------------------------------------------- # This file is part of the CMake build system for OGRE # (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ +# For the latest info, see https://www.ogre3d.org/ # # The contents of this file are placed in the public domain. Feel # free to make use of it in any way you like. diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake index 285740b63..ee6414646 100644 --- a/cmake/FindLIBUNSHIELD.cmake +++ b/cmake/FindLIBUNSHIELD.cmake @@ -4,7 +4,7 @@ # 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 (http://openmw.org), based on FindMPG123.cmake +# 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 diff --git a/cmake/FindOpenGLES.cmake b/cmake/FindOpenGLES.cmake deleted file mode 100644 index 7ee2c07f1..000000000 --- a/cmake/FindOpenGLES.cmake +++ /dev/null @@ -1,94 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find OpenGLES -# Once done this will define -# -# OPENGLES_FOUND - system has OpenGLES -# OPENGLES_INCLUDE_DIR - the GL include directory -# OPENGLES_LIBRARIES - Link these to use OpenGLES - -IF (WIN32) - IF (CYGWIN) - - FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h ) - - FIND_LIBRARY(OPENGLES_gl_LIBRARY libgles_cm ) - - ELSE (CYGWIN) - - IF(BORLAND) - SET (OPENGLES_gl_LIBRARY import32 CACHE STRING "OpenGL ES 1.x library for win32") - ELSE(BORLAND) - #MS compiler - todo - fix the following line: - SET (OPENGLES_gl_LIBRARY ${OGRE_SOURCE_DIR}/Dependencies/lib/release/libgles_cm.lib CACHE STRING "OpenGL ES 1.x library for win32") - ENDIF(BORLAND) - - ENDIF (CYGWIN) - -ELSE (WIN32) - - IF (APPLE) - - #create_search_paths(/Developer/Platforms) - #findpkg_framework(OpenGLES) - #set(OPENGLES_gl_LIBRARY "-framework OpenGLES") - - ELSE(APPLE) - - FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h - /opt/vc/include - /opt/graphics/OpenGL/include - /usr/openwin/share/include - /usr/X11R6/include - /usr/include - ) - - FIND_LIBRARY(OPENGLES_gl_LIBRARY - NAMES GLES_CM GLESv1_CM - PATHS /opt/vc/lib - /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/shlib /usr/X11R6/lib - /usr/lib - ) - - # On Unix OpenGL most certainly always requires X11. - # Feel free to tighten up these conditions if you don't - # think this is always true. - - #IF (OPENGLES_gl_LIBRARY) - # IF(NOT X11_FOUND) - # INCLUDE(FindX11) - # ENDIF(NOT X11_FOUND) - # IF (X11_FOUND) - # SET (OPENGLES_LIBRARIES ${X11_LIBRARIES}) - # ENDIF (X11_FOUND) - #ENDIF (OPENGLES_gl_LIBRARY) - - ENDIF(APPLE) -ENDIF (WIN32) - -SET( OPENGLES_FOUND "NO" ) -IF(OPENGLES_gl_LIBRARY) - - SET( OPENGLES_LIBRARIES ${OPENGLES_gl_LIBRARY} ${OPENGLES_LIBRARIES}) - - SET( OPENGLES_FOUND "YES" ) - -ENDIF(OPENGLES_gl_LIBRARY) - -MARK_AS_ADVANCED( - OPENGLES_INCLUDE_DIR - OPENGLES_gl_LIBRARY -) - -INCLUDE(FindPackageHandleStandardArgs) - -FIND_PACKAGE_HANDLE_STANDARD_ARGS(OPENGLES REQUIRED_VARS OPENGLES_LIBRARIES OPENGLES_INCLUDE_DIR) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index fe2837a09..2fa86094f 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -172,7 +172,7 @@ macro (get_generator_is_multi_config VALUE) 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}) + 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) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a0b426a16..ecb844a9b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -26,12 +26,6 @@ else (GIT_CHECKOUT) configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) endif (GIT_CHECKOUT) -if (OPENGL_ES) - find_package(OpenGLES REQUIRED) -else() - find_package(OpenGL REQUIRED) -endif() - # source files add_component_dir (settings @@ -191,12 +185,6 @@ include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -if (OPENGL_ES) - set(GL_LIB ${OPENGLES_gl_LIBRARY}) -else() - set(GL_LIB ${OPENGL_gl_LIBRARY}) -endif() - target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -213,8 +201,7 @@ target_link_libraries(components ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} ${SDL2_LIBRARIES} - # For MyGUI platform - ${GL_LIB} + ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} ) @@ -229,7 +216,7 @@ if (USE_QT) ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY}) else() - qt5_use_modules(components Widgets Core) + target_link_libraries(components Qt5::Widgets Qt5::Core) endif() endif() diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index c4be428b3..8905a86a1 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (bsa_file.cpp) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 5ff86ef65..196dc30fb 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (bsa_file.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 52a9a63f1..c9e205b8a 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -119,6 +119,11 @@ namespace Compiler return false; } } + else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState)) + { + // ignoring comma (for now) + return true; + } return Parser::parseSpecial (code, loc, scanner); } diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index c7f82a3d0..2d551348d 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -512,7 +512,7 @@ namespace Compiler return true; } - if (code==Scanner::S_member && mState==PotentialExplicitState) + if (code==Scanner::S_member && mState==PotentialExplicitState && mAllowExpression) { mState = MemberState; parseExpression (scanner, loc); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index b35612ee4..29dbe0391 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -493,8 +493,10 @@ 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).contains(".omwgame") || content.at(i).contains(".esm")) { + 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; } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index a7ac29b46..41407ec88 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -512,7 +512,9 @@ void ContentSelectorModel::ContentModel::sortFiles() //dependencies appear. for (int j = i + 1; j < fileCount; j++) { - if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive)) + 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); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f4da7e6ad..5e16064f4 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -239,4 +239,4 @@ void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); -} +} \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 9f775d597..323f926ed 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -15,7 +15,6 @@ namespace ContentSelectorView Q_OBJECT QMenu *mContextMenu; - QStringList mFilePaths; protected: @@ -61,6 +60,7 @@ namespace ContentSelectorView void signalCurrentGamefileIndexChanged (int); void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); + void signalSelectedFilesChanged(QStringList selectedFiles); private slots: diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index c39ef8269..335863c32 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -35,17 +35,20 @@ namespace AiSequence 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"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); } @@ -54,6 +57,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); @@ -63,6 +67,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); @@ -76,6 +81,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); @@ -154,6 +160,8 @@ namespace AiSequence break; } } + + esm.writeHNT ("LAST", mLastAiPackage); } void AiSequence::load(ESMReader &esm) @@ -221,6 +229,8 @@ namespace AiSequence return; } } + + esm.getHNOT (mLastAiPackage, "LAST"); } } } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index 52446d38f..d8c20185f 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -80,6 +80,7 @@ namespace ESM struct AiTravel : AiPackage { AiTravelData mData; + bool mHidden; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -89,6 +90,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; @@ -101,6 +103,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; @@ -147,10 +150,14 @@ namespace ESM struct AiSequence { - AiSequence() {} + AiSequence() + { + mLastAiPackage = -1; + } ~AiSequence(); std::vector mPackages; + int mLastAiPackage; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index e41201d6e..f35149586 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -173,10 +173,10 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool } if (!inInventory) + { esm.writeHNOCString ("KNAM", mKey); - - if (!inInventory) esm.writeHNOCString ("TNAM", mTrap); + } if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); @@ -188,7 +188,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -205,7 +205,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2a716427e..67b9d6a38 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -57,6 +57,7 @@ void ESMReader::close() mCtx.subCached = false; mCtx.recName.clear(); mCtx.subName.clear(); + mHeader.blank(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index a68c97a6a..dd34934ad 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -62,12 +62,23 @@ namespace ESM if (esm.getSubSize() == 52) { mNpdtType = NPC_DEFAULT; - esm.getExact(&mNpdt52, 52); + esm.getExact(&mNpdt, 52); } else if (esm.getSubSize() == 12) { + //Reading into temporary NPDTstruct12 object + NPDTstruct12 npdt12; mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - esm.getExact(&mNpdt12, 12); + 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"); @@ -135,9 +146,19 @@ namespace ESM esm.writeHNOCString("SCRI", mScript); if (mNpdtType == NPC_DEFAULT) - esm.writeHNT("NPDT", mNpdt52, 52); + { + esm.writeHNT("NPDT", mNpdt, 52); + } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) - esm.writeHNT("NPDT", mNpdt12, 12); + { + 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", mFlags); @@ -171,25 +192,7 @@ namespace ESM void NPC::blank() { mNpdtType = NPC_DEFAULT; - mNpdt52.mLevel = 0; - mNpdt52.mStrength = mNpdt52.mIntelligence = mNpdt52.mWillpower = mNpdt52.mAgility = - mNpdt52.mSpeed = mNpdt52.mEndurance = mNpdt52.mPersonality = mNpdt52.mLuck = 0; - for (int i=0; i< Skill::Length; ++i) mNpdt52.mSkills[i] = 0; - mNpdt52.mReputation = 0; - mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0; - mNpdt52.mDisposition = 0; - mNpdt52.mFactionID = 0; - mNpdt52.mRank = 0; - mNpdt52.mUnknown = 0; - mNpdt52.mGold = 0; - mNpdt12.mLevel = 0; - mNpdt12.mDisposition = 0; - mNpdt12.mReputation = 0; - mNpdt12.mRank = 0; - mNpdt12.mUnknown1 = 0; - mNpdt12.mUnknown2 = 0; - mNpdt12.mUnknown3 = 0; - mNpdt12.mGold = 0; + blankNpdt(); mFlags = 0; mInventory.mList.clear(); mSpells.mList.clear(); @@ -207,14 +210,27 @@ namespace ESM 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.mFactionID = 0; + mNpdt.mRank = 0; + mNpdt.mUnknown = 0; + mNpdt.mGold = 0; + } + int NPC::getFactionRank() const { if (mFaction.empty()) return -1; - else if (mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - return mNpdt12.mRank; - else // NPC_DEFAULT - return mNpdt52.mRank; + else + return mNpdt.mRank; } const std::vector& NPC::getTransport() const diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index d7f30e079..5f567d999 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -95,6 +95,8 @@ struct NPC int mGold; }; // 52 bytes + //Structure for autocalculated characters. + // This is only used for load and save operations. struct NPDTstruct12 { short mLevel; @@ -106,8 +108,9 @@ struct NPC #pragma pack(pop) unsigned char mNpdtType; - NPDTstruct52 mNpdt52; - NPDTstruct12 mNpdt12; //for autocalculated characters + //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 @@ -141,6 +144,9 @@ struct NPC void blank(); ///< Set record to default state (does not touch the ID). + + /// Resets the mNpdt object + void blankNpdt(); }; } #endif diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 6e0e6db9d..e708bbb4e 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -109,8 +109,6 @@ namespace ESM void Region::blank() { - mName.clear(); - mData.mClear = mData.mCloudy = mData.mFoggy = mData.mOvercast = mData.mRain = mData.mThunder = mData.mAsh, mData.mBlight = mData.mA = mData.mB = 0; diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 930cdb849..f80875b65 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -26,13 +26,23 @@ struct Static /// 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; + std::string mId, mModel; - void load(ESMReader &esm, bool &isDeleted); - void save(ESMWriter &esm, bool isDeleted = false) 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). + + Static(const std::string id, const std::string &model) + : mId(id) + , mModel(model) + { + } + + Static() + { + } }; } #endif diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index eddcaee4f..8b48b71b7 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -57,7 +57,7 @@ struct Weapon float mWeight; int mValue; short mType; - short mHealth; + unsigned short mHealth; float mSpeed, mReach; short mEnchant; // Enchantment points. The real value is mEnchant/10.f unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 9ec53240a..7dad34dfb 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -31,6 +31,18 @@ void ESM::Player::load (ESMReader &esm) mPaidCrimeId = -1; esm.getHNOT (mPaidCrimeId, "PAYD"); + bool checkPrevItems = true; + while (checkPrevItems) + { + std::string boundItemId = esm.getHNOString("BOUN"); + std::string prevItemId = esm.getHNOString("PREV"); + + if (!boundItemId.empty()) + mPreviousItems[boundItemId] = prevItemId; + else + checkPrevItems = false; + } + if (esm.hasMoreSubs()) { for (int i=0; ifirst); + esm.writeHNString ("PREV", it->second); + } + for (int i=0; i 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; }; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3220f496e..ea9fef4fb 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 3; +int ESM::SavedGame::sCurrentFormat = 5; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 63efb36be..dadc64f57 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -232,9 +232,9 @@ namespace ESMTerrain // 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 (colStart == 0 && vertY_ != 0) + if (vertY_ != 0) colStart += increment; - if (rowStart == 0 && vertX_ != 0) + if (vertX_ != 0) rowStart += increment; // Only relevant for chunks smaller than (contained in) one cell @@ -430,13 +430,16 @@ namespace ESMTerrain // Second iteration - create and fill in the blend maps 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; for (int i=0; i image (new osg::Image); - image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); + image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); for (int y=0; y(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - if (blendIndex == i) - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255; - else - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; + int alpha = (blendIndex == i) ? 255 : 0; + + int realY = (blendmapSize - y - 1)*imageScaleFactor; + int realX = x*imageScaleFactor; + + pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha; + pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha; + pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha; + pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha; } } - blendmaps.push_back(image); } } diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index 11a577a45..edc3f2678 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -25,7 +25,7 @@ namespace Fallback float Map::getFallbackFloat(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return 0; else return boost::lexical_cast(fallback); @@ -34,7 +34,7 @@ namespace Fallback int Map::getFallbackInt(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return 0; else return std::stoi(fallback); @@ -43,7 +43,7 @@ namespace Fallback bool Map::getFallbackBool(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return false; else return stob(fallback); @@ -52,8 +52,8 @@ namespace Fallback osg::Vec4f Map::getFallbackColour(const std::string& fall) const { std::string sum=getFallbackString(fall); - if(sum.empty()) - return osg::Vec4f(0.f,0.f,0.f,1.f); + if (sum.empty()) + return osg::Vec4f(0.5f,0.5f,0.5f,1.f); else { std::string ret[3]; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index aa92a01c6..080739bc1 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -339,52 +339,76 @@ namespace Gui + MyGUI::utility::toString((fontSize-data[i].ascent))); code->addAttribute("size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); - // More hacks! The french game uses several win1252 characters that are not included - // in the cp437 encoding of the font. Fall back to similar available characters. - if (mEncoding == ToUTF8::CP437) + // 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) { - std::multimap additional; // - additional.insert(std::make_pair(39, 0x2019)); // apostrophe - additional.insert(std::make_pair(45, 0x2013)); // dash - additional.insert(std::make_pair(45, 0x2014)); // dash - additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark - additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark - additional.insert(std::make_pair(44, 0x201A)); - additional.insert(std::make_pair(44, 0x201E)); - additional.insert(std::make_pair(43, 0x2020)); - additional.insert(std::make_pair(94, 0x02C6)); - additional.insert(std::make_pair(37, 0x2030)); - additional.insert(std::make_pair(83, 0x0160)); - additional.insert(std::make_pair(60, 0x2039)); - additional.insert(std::make_pair(79, 0x0152)); - additional.insert(std::make_pair(90, 0x017D)); - additional.insert(std::make_pair(39, 0x2019)); - additional.insert(std::make_pair(126, 0x02DC)); - additional.insert(std::make_pair(84, 0x2122)); - additional.insert(std::make_pair(83, 0x0161)); - additional.insert(std::make_pair(62, 0x203A)); - additional.insert(std::make_pair(111, 0x0153)); - additional.insert(std::make_pair(122, 0x017E)); - additional.insert(std::make_pair(89, 0x0178)); - additional.insert(std::make_pair(156, 0x00A2)); - additional.insert(std::make_pair(46, 0x2026)); - - 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))); - } + 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 @@ -424,7 +448,7 @@ namespace Gui MyGUI::FontCodeType::Enum type; if(i == 0) type = MyGUI::FontCodeType::Selected; - else if (i == 1) + else // if (i == 1) type = MyGUI::FontCodeType::SelectedBack; MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index df0fc687e..e402f0b79 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -1,28 +1,31 @@ #include "rng.hpp" -#include -#include + +#include +#include namespace Misc { + std::mt19937 Rng::generator = std::mt19937(); + void Rng::init() { - std::srand(static_cast(std::time(NULL))); + generator.seed(static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count())); } float Rng::rollProbability() { - return static_cast(std::rand() / (static_cast(RAND_MAX)+1.0)); + return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(generator); } float Rng::rollClosedProbability() { - return static_cast(std::rand() / static_cast(RAND_MAX)); + return std::uniform_real_distribution(0, 1)(generator); } int Rng::rollDice(int max) { - return static_cast((std::rand() / (static_cast(RAND_MAX)+1.0)) * (max)); + return max > 0 ? std::uniform_int_distribution(0, max - 1)(generator) : 0; } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 01fcdd763..ff56906d9 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_MISC_RNG_H #include +#include namespace Misc { @@ -13,6 +14,9 @@ class Rng { public: + /// create a RNG + static std::mt19937 generator; + /// seed the RNG static void init(); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 9f4931d72..0fde1c96c 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -6,6 +6,8 @@ #include #include +#include "utf8stream.hpp" + namespace Misc { class StringUtils @@ -56,6 +58,70 @@ public: }; } + 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()); } diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 368374a64..e499d15e6 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -1,6 +1,7 @@ #ifndef MISC_UTF8ITER_HPP #define MISC_UTF8ITER_HPP +#include #include class Utf8Stream diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 5601474ac..be48e912e 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (controlled.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index f39132543..49f591b47 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -101,6 +101,18 @@ namespace Nif data.post(nif); } + void NiLookAtController::read(NIFStream *nif) + { + Controller::read(nif); + target.read(nif); + } + + void NiLookAtController::post(NIFFile *nif) + { + Controller::post(nif); + target.post(nif); + } + void NiPathController::read(NIFStream *nif) { Controller::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 0861dfa6b..f22d10622 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (controller.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ @@ -99,6 +99,15 @@ public: void post(NIFFile *nif); }; +class NiLookAtController : public Controller +{ +public: + NodePtr target; + + void read(NIFStream *nif); + void post(NIFFile *nif); +}; + class NiUVController : public Controller { public: diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 9b4a3a67c..6b7aa579b 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (data.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 015809a68..453e4b04c 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (effect.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 1e5a8616d..d935add55 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (extra.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b4b1caefc..4061247b5 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -108,6 +108,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct , RC_NiSequenceStreamHelper )); newFactory.insert(makeEntry("NiSourceTexture", &construct , RC_NiSourceTexture )); newFactory.insert(makeEntry("NiSkinInstance", &construct , RC_NiSkinInstance )); + newFactory.insert(makeEntry("NiLookAtController", &construct , RC_NiLookAtController )); return newFactory; } diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index 5827448fd..778625717 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (nif_types.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 96156c6d8..f46f8ef27 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (property.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 605c4d76e..ee4d508ab 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -2,7 +2,7 @@ OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ + WWW: https://openmw.org/ This file (record.h) is part of the OpenMW package. @@ -17,7 +17,7 @@ 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/ . + https://www.gnu.org/licenses/ . */ @@ -93,7 +93,8 @@ enum RecordType RC_NiSourceTexture, RC_NiSkinInstance, RC_RootCollisionNode, - RC_NiSphericalCollider + RC_NiSphericalCollider, + RC_NiLookAtController }; /// Base class for all records diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6e04c6405..aada21fce 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -261,7 +261,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -275,7 +275,6 @@ namespace NifOsg for (unsigned int i=0; igetNumChildren(); ++i) skel->addChild(root->getChild(i)); root->removeChildren(0, root->getNumChildren()); - created = skel; } else skel->addChild(created); @@ -464,7 +463,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return NULL; @@ -511,7 +510,7 @@ namespace NifOsg if(sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. - skipMeshes = true; + hasMarkers = true; } } } @@ -543,7 +542,7 @@ namespace NifOsg node->setNodeMask(0x1); } - if (skipMeshes && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. + if ((skipMeshes || hasMarkers) && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. { node->setDataVariance(osg::Object::DYNAMIC); } @@ -555,13 +554,18 @@ namespace NifOsg if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) { const Nif::NiTriShape* triShape = static_cast(nifNode); - if (triShape->skin.empty()) - handleTriShape(triShape, node, composite, boundTextures, animflags); - else - handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); + const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name); + static const std::string pattern = "tri editormarker"; + if (!hasMarkers || nodeName.compare(0, pattern.size(), pattern) != 0) + { + if (triShape->skin.empty()) + handleTriShape(triShape, node, composite, boundTextures, animflags); + else + handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); - if (!nifNode->controller.empty()) - handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + if (!nifNode->controller.empty()) + handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + } } if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) @@ -599,7 +603,7 @@ namespace NifOsg for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, isAnimated, textKeys, rootNode); + handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); } } diff --git a/components/sceneutil/controller.hpp b/components/sceneutil/controller.hpp index 775cb23b0..d02b65cf1 100644 --- a/components/sceneutil/controller.hpp +++ b/components/sceneutil/controller.hpp @@ -25,6 +25,8 @@ namespace SceneUtil 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. diff --git a/components/sceneutil/morphgeometry.hpp b/components/sceneutil/morphgeometry.hpp index 122c1456c..ba3b40961 100644 --- a/components/sceneutil/morphgeometry.hpp +++ b/components/sceneutil/morphgeometry.hpp @@ -21,6 +21,9 @@ namespace SceneUtil /// @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. + virtual void compileGLObjects(osg::RenderInfo& renderInfo) const {} + class MorphTarget { protected: diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 64f4bf312..60b3edc9d 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -23,8 +23,8 @@ namespace SceneUtil META_Object(SceneUtil, RigGeometry) - // At this point compileGLObjects() remains unimplemented, hard to avoid race conditions - // and there is limited value in compiling anyway since the data will change again for the next frame + // 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. + virtual void compileGLObjects(osg::RenderInfo& renderInfo) const {} struct BoneInfluence { diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index de82ca850..f9767238f 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -91,7 +91,7 @@ void GraphicsWindowSDL2::init() SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); -#if defined(OPENGL_ES) || defined(ANDROID) +#if defined(ANDROID) int major = 1; int minor = 1; char *ver = getenv("OPENMW_GLES_VERSION"); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 2e7b5a8ae..15a222d31 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -292,7 +292,7 @@ public: ostream << "# to its default, simply remove it from this file. For available" << std::endl; ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; ostream << "#" << std::endl; - ostream << "# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << 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. diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 640f2932b..56ace0e5a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -25,6 +25,9 @@ namespace Terrain 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 = new osg::TexMat(matrix); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index f31064805..052090dc8 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -189,35 +189,22 @@ public: node->setViewDataMap(mViewDataMap); parent->addChild(node); - if (center.x() - size > mMaxX - || center.x() + size < mMinX - || center.y() - size > mMaxY - || center.y() + size < 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; - } - - if (node->getSize() <= mMinSize) - { - // We arrived at a leaf - float minZ,maxZ; - if (mStorage->getMinMaxHeights(size, center, minZ, maxZ)) - { - float cellWorldSize = mStorage->getCellWorldSize(); - osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ), - osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ)); - node->setBoundingBox(boundingBox); - } - return node; - } - else + if (node->getSize() > mMinSize) { addChildren(node); return node; } + + // We arrived at a leaf + float minZ, maxZ; + mStorage->getMinMaxHeights(size, center, minZ, maxZ); + + float cellWorldSize = mStorage->getCellWorldSize(); + osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ), + osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ)); + node->setBoundingBox(boundingBox); + + return node; } osg::ref_ptr getRootNode() @@ -246,7 +233,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour QuadTreeWorld::~QuadTreeWorld() { - ensureQuadTreeBuilt(); mViewDataMap->clear(); } diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index ab0739c2c..a3b0ae28a 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -5,13 +5,20 @@ namespace Gui { + bool ImageButton::sDefaultNeedKeyFocus = true; + ImageButton::ImageButton() : Base() , mMouseFocus(false) , mMousePress(false) , mKeyFocus(false) { - setNeedKeyFocus(true); + setNeedKeyFocus(sDefaultNeedKeyFocus); + } + + void ImageButton::setDefaultNeedKeyFocus(bool enabled) + { + sDefaultNeedKeyFocus = enabled; } void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value) diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index 509b1c8c2..bfcff7997 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -18,12 +18,16 @@ namespace Gui 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); private: void updateImage(); + static bool sDefaultNeedKeyFocus; + protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); virtual void onMouseLostFocus(MyGUI::Widget* _new); diff --git a/components/widgets/numericeditbox.cpp b/components/widgets/numericeditbox.cpp index ee2c30a7b..efe480653 100644 --- a/components/widgets/numericeditbox.cpp +++ b/components/widgets/numericeditbox.cpp @@ -38,11 +38,11 @@ namespace Gui setCaption(MyGUI::utility::toString(mValue)); } } - catch (std::invalid_argument) + catch (const std::invalid_argument&) { setCaption(MyGUI::utility::toString(mValue)); } - catch (std::out_of_range) + catch (const std::out_of_range&) { setCaption(MyGUI::utility::toString(mValue)); } diff --git a/docs/Doxyfile.cmake b/docs/Doxyfile.cmake index 38ad84165..71ce32069 100644 --- a/docs/Doxyfile.cmake +++ b/docs/Doxyfile.cmake @@ -20,7 +20,7 @@ # 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 http://www.gnu.org/software/libiconv +# 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. @@ -295,7 +295,7 @@ 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 http://daringfireball.net/projects/markdown/ for details. +# 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. @@ -328,7 +328,7 @@ BUILTIN_STL_SUPPORT = YES CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# 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. @@ -687,7 +687,7 @@ 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 http://en.wikipedia.org/wiki/BibTeX for more info. +# 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 @@ -772,7 +772,7 @@ INPUT = @OpenMW_SOURCE_DIR@/apps \ # 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: http://www.gnu.org/software/libiconv) for the list of +# documentation (see: https://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. @@ -993,7 +993,7 @@ 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 http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: @@ -1136,7 +1136,7 @@ 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 -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 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. @@ -1194,7 +1194,7 @@ 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: http://developer.apple.com/tools/xcode/), introduced with +# 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 @@ -1239,7 +1239,7 @@ 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: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# (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 @@ -1315,7 +1315,7 @@ 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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#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. @@ -1323,8 +1323,7 @@ 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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# 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. @@ -1332,23 +1331,21 @@ 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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# 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: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# 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: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# 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 = @@ -1453,7 +1450,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# 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 @@ -1465,7 +1462,7 @@ 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: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# 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. @@ -1480,11 +1477,11 @@ MATHJAX_FORMAT = HTML-CSS # 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 http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# 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 = http://cdn.mathjax.org/mathjax/latest +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 @@ -1495,7 +1492,7 @@ 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: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (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. @@ -1542,7 +1539,7 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer ( doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1555,7 +1552,7 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer ( doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Xapian (see: https://xapian.org/). See the section "External Indexing and # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1726,7 +1723,7 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1882,7 +1879,7 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen -# Definitions (see http://autogen.sf.net) file that captures the structure of +# Definitions (see http://autogen.sourceforge.net) file that captures the structure of # the code including all documentation. Note that this feature is still # experimental and incomplete at the moment. # The default value is: NO. @@ -2093,7 +2090,7 @@ HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: YES. diff --git a/docs/DoxyfilePages.cmake b/docs/DoxyfilePages.cmake index d50a043d6..f3454c9d0 100644 --- a/docs/DoxyfilePages.cmake +++ b/docs/DoxyfilePages.cmake @@ -18,7 +18,7 @@ # 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 -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# https://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 @@ -581,7 +581,7 @@ INPUT = @OpenMW_SOURCE_DIR@/apps \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# into libc) for the transcoding. See https://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 @@ -753,7 +753,7 @@ REFERENCES_LINK_SOURCE = 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 http://www.gnu.org/software/global/global.html). You +# tagging system (see https://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO @@ -928,30 +928,30 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see -# Qt Help Project / Custom Filters. +# Qt Help Project / Custom Filters. 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. +# Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = diff --git a/docs/cs-manual/source/record-filters.rst b/docs/cs-manual/source/record-filters.rst new file mode 100644 index 000000000..3379f557f --- /dev/null +++ b/docs/cs-manual/source/record-filters.rst @@ -0,0 +1,293 @@ +Record Filters +############## + +Filters are a key element of the OpenMW CS user interface, they allow rapid and +easy access to records presented in all tables. In order to use this +application effectively you need to familiarise yourself with all the concepts +and instructions explained in this chapter. The filter system is somewhat +unusual at first glance, but once you understand the basics it will be fairly +intuitive and easy to use + +Filters are a key element to using the OpenMW CS efficiently by allowing you to +narrow down the table entries very quickly and find what you are looking for. +The filter system might appear unusual at first, you don't just type in a word +and get all instances where it occurs, instead filters are first-class objects +in the CS with their own table. This allows you to define very specific filters +for your project and store them on disc to use in the next session. The CS +allows you fine-grained control, you can choose whether to make a filter +persistent between session, only for one session or use a one-off filter by +typing it directly into the filter field. + + + +Terms used +********** + +Filter + A Filter is generally speaking a tool able to filter the elements of a + table, that is select some elements while discarding others, according to + some criteria. These criteria are written using their own syntax. + +Criterion + A criterion describes some condition a record needs to satisfy in order to + be selected. They are written using a special syntax which is explained + below. We can logically combine multiple criteria in a filter for finer + control. + +Expression + Expressions are how we perform filtering. They look like functions in a + programming language: they have a name and accept a number of arguments. + The expression evaluates to either ``true`` or ``false`` for every record in + the table. The arguments are expressions themselves. + +Arity + The arity of an expression tells us how many arguments it takes. Expressions + taking no arguments are called *nullary*, those taking one argument are + known as *unary* expressions and those taking two arguments are called + *binary*. + + + +Interface +********* + +Above each table there is a text field which is used to enter a filter: either +one predefined by the OpenMW CS developers or one made by you. Another +important element is the filter table found under *View* → *Filters*. You +should see the default filters made by the OpenMW team in the table. The table +has the columns *Filter*, *Description* and *Modified*. + +ID + A unique name used to refer to this filter. Note that every ID has a + scope prefix, we will explain these soon. + +Modified + This is the same as for all the other records, it tells us whether the + filter is *added* or *removed*. Filters are specific to a project instead of + a content file, they have no effect on the game itself. + +Filter + The actual contents of the filter are given here using the filter syntax. + Change the expressions to modify what the filter returns. + +Description + A textual description of what the filter does. + + + +Using predefined filters +************************ + +To use a filter you have to type its ID into the filter field above a table. + +For instance, try to opening the objects table (under the world menu) and type +into the filters field ``project::weapons``. As soon as you complete the text +the table will show only the weapons. The string ``project::weapons`` is the ID +of one of the predefined filters. This means that in order to use the filter +inside the table you type its name inside the filter field. + +Filter IDs follow these general conventions: + +- IDs of filters for a specific record type contain usually the name of a + specific group. For instance the ``project::weapons`` filter contains the + term ``weapons``. Plural form is always used. + +- When filtering a specific subgroup the ID is prefixed with the name of the + more general filter. For instance ``project::weaponssilver`` will filter only + silver weapons and ``project::weaponsmagical`` will filter only magical + weapons. + +- There are few exceptions from the above rule. For instance there are + ``project::added``, ``project::removed``, ``project::modified`` and + ``project::base``. You might except something more like + ``project::statusadded`` but in this case requiring these extra characters + would not improve readability. + +We strongly recommend you take a look at the filters table right now to see +what you can filter with the defaults. Try using the default filters first +before writing you own. + + + +Writing your own filters +************************ + +As mentioned before, filters are just another type of record in the OpenMW CS. +To create a new filter you will have to add a new record to the *Filters* table +and set its properties to your liking. Filters are created by combining +existing filters into more complex ones. + + +Scopes +====== + +Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism +that determines the lifetime of the filter. These are the supported scopes: + +``project::`` + Indicates that the filter is to be used throughout the project in multiple + sessions. You can restart the CS and the filter will still be there. + +``session::`` + Indicates that the filter is not stored between multiple sessions and once + you quit the OpenMW CS application the filter will be gone. Until then it + can be found inside the filters table. + +Project-filters are stored in an internal project file, not final content file +meant for the player. Keep in mind when collaborating with other modders that +you need to share the same project file. + + + +Writing expressions +=================== + +The syntax for expressions is as follows: + +.. code-block:: + + + () + (, , ..., ) + +Where ```` is the name of the expression, such as ``string`` and the +```` are expressions themselves. A nullary expression consists only of its +name. A unary expression contains its argument within a pair of parentheses +following the name. If there is more than one argument they are separated by +commas inside the parentheses. + +An example of a binary expression is ``string("Record Type", weapon)``; the +name is ``string``, and it takes two arguments which are strings of string +type. The meaning of arguments depends on the expression itself. In this case +the first argument is the name of a record column and the second field is the +values we want to test it against. + +Strings are sequences of characters and are case-insensitive. If a string +contains spaces it must be quoted, otherwise the quotes are optional and +ignored. + + +Constant Expressions +-------------------- + +These expressions take no arguments and always return the same result. + +``true`` + Always evaluates to ``true``. + +``false`` + Always evaluates to ``false``. + + +Comparison Expressions +---------------------- + +``string(, )`` + The ```` is a regular expression pattern. The expressions evaluates + to ``true`` when the value of a record in ```` matches the pattern. + Since the majority of the columns contain string values, ``string`` is among + the most often used expressions. Examples: + + ``string("Record Type", "Weapon")`` + Will evaluate to ``true`` for all records containing ``Weapon`` in the + *Record Type* column cell. + + ``string("Portable", "true")`` + Will evaluate to ``true`` [#]_ for all records containing word ``true`` inside + *Portable* column cell. + +.. [#] There is no Boolean (``true`` or ``false``) value in the OpenMW CS. You + should use a string for those. + + +``value(, (, ))`` + Match a value type, such as a number, with a range of possible values. The + argument ```` is the string name of the value we want to compare, the + second argument is a pair of lower and upper bounds for the range interval. + + One can use either parentheses ``()`` or brackets ``[]`` to surround the + pair. Brackets are inclusive and parentheses are exclusive. We can also mix + both styles: + + .. code:: + + value("Weight", [20, 50)) + + This will match any objects with a weight greater or equal to 20 and + strictly less than 50. + + +Logical Expressions +------------------- + +``not `` + Logically negates the result of an expression. If ```` evaluates + to ``true`` the negation is ``false``, and if ```` evaluates to + ``false`` the negation is ``true``. Note that there are no parentheses + around the argument. + +``or(, , ..., )`` + Logical disjunction, evaluates to ``true`` if at least one argument + evaluates to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for both NPCs and creatures; the + expression for that use-case is + + .. code:: + + or(string("record type", "npc"), string("record type", "creature")) + + In this particular case only one argument can evaluate to ``true``, but one + can write expressions where multiple arguments can be ``true`` at a time. + +``or(, , ..., )`` + Logical conjunction, evaluates to ``true`` if and only if all arguments + evaluate to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for weapons weighting less than a hundred + units The expression for that use-case is + + .. code:: + + and(string("record type", "weapon"), value("weight", (0, 100))) + + +Anonymous filters +================= + +Creating a whole new filter when you only intend to use it once can be +cumbersome. For that reason the OpenMW CS supports *anonymous* filters which +can be typed directly into the filters field of a table. They are not stored +anywhere, when you clear the field the filter is gone forever. + +In order to define an anonymous filter you type an exclamation mark as the +first character into the field followed by the filter definition (e.g. +``!string("Record Type", weapon)`` to filter only for weapons). + + + +Creating and saving filters +*************************** + +Filters are managed the same way as other records: go to the filters table, +right click and select the option *Add Record* from the context menu. You are +given a choice between project- or session scope. Choose the scope from the +dropdown and type in your desired ID for the filter. A newly created filter +does nothing since it still lacks expressions. In order to add your queries you +have to edit the filter record. + + +Replacing the default filters set +================================= + +OpenMW CS allows you to substitute the default filter set for the entire +application. This will affect the default filters for all content files that +have not been edited on this computer and user account. + +Create a new content file, add the desired filters, remove the undesired ones +and save. Now rename the *project* file to ``defaultfilters`` and make sure the +``.omwaddon.project`` file extension is removed. This file will act as a +template for all new files from now on. If you wish to go back to the +old default set rename or remove this custom file. diff --git a/docs/cs-manual/source/record-types.rst b/docs/cs-manual/source/record-types.rst new file mode 100644 index 000000000..3742cc9e8 --- /dev/null +++ b/docs/cs-manual/source/record-types.rst @@ -0,0 +1,62 @@ +Record Types +############ + +A game world contains many items, such as chests, weapons and monsters. All +these items are merely instances of templates we call *Objects*. The OpenMW CS +*Objects* table contains information about each of these template objects, such +as its value and weight in the case of items, or an aggression level in the +case of NPCs. + +The following is a list of all Record Types and what you can tell OpenMW CS +about each of them. + +Activator + Activators can have a script attached to them. As long as the cell this + object is in is active the script will be run once per frame. + +Potion + This is a potion which is not self-made. It has an Icon for your inventory, + weight, coin value, and an attribute called *Auto Calc* set to ``False``. + This means that the effects of this potion are pre-configured. This does not + happen when the player makes their own potion. + +Apparatus + This is a tool to make potions. Again there’s an icon for your inventory as + well as a weight and a coin value. It also has a *Quality* value attached to + it: the higher the number, the better the effect on your potions will be. + The *Apparatus Type* describes if the item is a *Calcinator*, *Retort*, + *Alembic* or *Mortar & Pestle*. + +Armor + This type of item adds *Enchantment Points* to the mix. Every piece of + clothing or armor has a "pool" of potential *Magicka* that gets unlocked + when the player enchants it. Strong enchantments consume more magicka from + this pool: the stronger the enchantment, the more *Enchantment Points* each + cast will take up. *Health* means the amount of hit points this piece of + armor has. If it sustains enough damage, the armor will be destroyed. + Finally, *Armor Value* tells the game how much points to add to the player + character’s *Armor Rating*. + +Book + This includes scrolls and notes. For the game to make the distinction + between books and scrolls, an extra property, *Scroll*, has been added. + Under the *Skill* column a scroll or book can have an in-game skill listed. + Reading this item will raise the player’s level in that specific skill. + +Clothing + These items work just like armors, but confer no protective properties. + Rather than *Armor Type*, these items have a *Clothing Type*. + +Container + This is all the stuff that stores items, from chests to sacks to plants. Its + *Capacity* shows how much stuff you can put in the container. You can + compare it to the maximum allowed load a player character can carry. A + container, however, will just refuse to take the item in question when it + gets "over-encumbered". Organic Containers are containers such as plants. + Containers that respawn are not safe to store stuff in. After a certain + amount of time they will reset to their default contents, meaning that + everything in them is gone forever. + +Creature + These can be monsters, animals and the like. + diff --git a/docs/cs-manual/source/tables.rst b/docs/cs-manual/source/tables.rst new file mode 100644 index 000000000..43da03f07 --- /dev/null +++ b/docs/cs-manual/source/tables.rst @@ -0,0 +1,168 @@ +Tables +###### + +If you have launched OpenMW CS already and played around with it for a bit, you +will have noticed that the interface is made entirely of tables. This does not +mean it works just like a spreadsheet application though, it would be more +accurate to think of databases instead. Due to the vast amounts of information +involved with Morrowind tables made the most sense. You have to be able to spot +information quickly and be able to change them on the fly. + + +Used Terms +********** + +Record + An entry in OpenMW CS representing an item, location, sound, NPC or anything + else. + +Instance, Object + When an item is placed in the world, it does not create a whole new record + each time, but an *instance* of the *object*. + + For example, the game world might contain a lot of exquisite belts on + different NPCs and in many crates, but they all refer to one specific + instance: the Exquisite Belt record. In this case, all those belts in crates + and on NPCs are instances. The central Exquisite Belt instance is called an + *object*. This allows modders to make changes to all items of the same type + in one place. + + If you wanted all exquisite belts to have 4000 enchantment points rather + than 400, you would only need to change the object Exquisite Belt rather + than all exquisite belt instances individually. + +Some columns are recurring throughout OpenMW CS, they show up in (nearly) every +table. + +ID + Each item, location, sound, etc. gets the same unique identifier in both + OpenMW CS and Morrowind. This is usually a very self-explanatory name. For + example, the ID for the (unique) black pants of Caius Cosades is + ``Caius_pants``. This allows players to manipulate the game in many ways. + For example, they could add these pants to their inventory by opening the + console and entering: ``player- >addItem Caius_pants``. In both Morrowind + and OpenMW CS the ID is the primary way to identify all these different + parts of the game. + +Modified + This column shows what has happened (if anything) to this record. There are + four possible states in which it can exist: + + Base + The record is unmodified and from a content file other than the one + currently being edited. + + Added + This record has been added in the currently content file. + + Modified + Similar to *base*, but has been changed in some way. + + Deleted + Similar to *base*, but has been removed as an entry. This does not mean, + however, that the occurrences in the game itself have been removed! For + example, if you were to remove the ``CharGen_Bed`` entry from + ``morrowind.esm``, it does not mean the bedroll in the basement of the + Census and Excise Office in Seyda Neen will be gone. You will have to + delete that instance yourself or make sure that that object is replaced + by something that still exists otherwise the player will get crashes in + the worst case scenario. + + + +World Screens +************* + +The contents of the game world can be changed by choosing one of the options in +the appropriate menu at the top of the screen. + + +Regions +======= + +This describes the general areas of Vvardenfell. Each of these areas has +different rules about things such as encounters and weather. + +Name + This is how the game will show the player's location in-game. + +MapColour + This is a six-digit hexadecimal representation of the colour used to + identify the region on the map available in *World* → *Region Map*. + +Sleep Encounter + These are the rules for what kinds of enemies the player might encounter + when sleeping outside in the wilderness. + + +Cells +===== + +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot +going on simultaneously. But if the player is in Balmora, why would the +computer need to keep track the exact locations of NPCs walking through the +corridors in a Vivec canton? All that work would be quite useless and bring +the player's system down to its knees! So the world has been divided up into +squares we call *cells*. Once your character enters a cell, the game will load +everything that is going on in that cell so the player can interact with it. + +In the original Morrowind this could be seen when a small loading bar would +appear near the bottom of the screen while travelling; the player had just +entered a new cell and the game had to load all the items and NPCs. The *Cells* +screen in OpenMW CS provides you with a list of cells in the game, both the +interior cells (houses, dungeons, mines, etc.) and the exterior cells (the +outside world). + +Sleep Forbidden + Can the player sleep on the floor? In most cities it is forbidden to sleep + outside. Sleeping in the wilderness carries its own risks of attack, though, + and this entry lets you decide if a player should be allowed to sleep on the + floor in this cell or not. + +Interior Water + Should water be rendered in this interior cell? The game world consists of + an endless ocean at height 0, then the landscape is added. If part of the + landscape goes below height 0, the player will see water. + + Setting the cell’s Interior Water to true tells the game that this cell that + there needs to be water at height 0. This is useful for dungeons or mines + that have water in them. + + Setting the cell’s Interior Water to ``false`` tells the game that the water + at height 0 should not be used. This flag is useless for outside cells. + +Interior Sky + Should this interior cell have a sky? This is a rather unique case. The + Tribunal expansion took place in a city on the mainland. Normally this would + require the city to be composed of exterior cells so it has a sky, weather + and the like. But if the player is in an exterior cell and were to look at + their in-game map, they would see Vvardenfell with an overview of all + exterior cells. The player would have to see the city’s very own map, as if + they were walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The + whole city would technically work exactly like an interior cell, but it + would need a sky as if it was an exterior cell. That is what this is. This + is why the vast majority of the cells you will find in this screen will have + this option set to false: It is only meant for these "fake exteriors". + +Region + To which Region does this cell belong? This has an impact on the way the + game handles weather and encounters in this area. It is also possible for a + cell not to belong to any region. + + +Objects +======= + +This is a library of all the items, triggers, containers, NPCs, etc. in the +game. There are several kinds of Record Types. Depending on which type a record +is, it will need specific information to function. For example, an NPC needs a +value attached to its aggression level. A chest, of course, does not. All +Record Types contain at least a 3D model or else the player would not see them. +Usually they also have a *Name*, which is what the players sees when they hover +their reticle over the object during the game. + +Please refer to the Record Types chapter for an overview of what each type of +object does and what you can tell OpenMW CS about these objects. + diff --git a/docs/source/conf.py b/docs/source/conf.py index b18b40c50..0ba8567c0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,16 +60,32 @@ copyright = u'2017, OpenMW Team' # The short X.Y version. # The full version, including alpha/beta/rc tags. +release = version = "UNRELEASED" + + +def get_openmw_version(haystack): + needle = 'OPENMW_VERSION_MAJOR' + line_counter = 0 + for hay in haystack: + if needle in str(hay): + break + line_counter += 1 + + version = '.'.join([haystack[line_counter][1][1].contents, + haystack[line_counter+1][1][1].contents, + haystack[line_counter+2][1][1].contents]) + return version + + try: from parse_cmake import parsing cmake_raw = open(project_root+'/CMakeLists.txt', 'r').read() cmake_data = parsing.parse(cmake_raw) - release = version = '.'.join([cmake_data[24][1][1].contents, - cmake_data[25][1][1].contents, - cmake_data[26][1][1].contents]) -except ImportError: - release = "UNRELEASED" - print("WARNING: Unable to import parse_cmake, version will be set to: {0}.".format(release)) + release = version = get_openmw_version(cmake_data) + +except Exception as ex: + print("WARNING: Version will be set to '{0}' because: '{1}'.".format(release, str(ex))) + import traceback; traceback.print_exc() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/manuals/openmw-cs/files-and-directories.rst b/docs/source/manuals/openmw-cs/files-and-directories.rst index 77593dece..ae39082d7 100644 --- a/docs/source/manuals/openmw-cs/files-and-directories.rst +++ b/docs/source/manuals/openmw-cs/files-and-directories.rst @@ -145,7 +145,7 @@ and a place where OpenMW CS looks for already existing files. Resource files ============== -.. TODO This paragraph sounds weird +.. TODO This paragraph sounds weird Unless we are talking about a fully text based game, like Zork or Rogue, one would expect that a video game is using some media files: 3D models with @@ -219,6 +219,6 @@ files for textures. .. Hyperlink targets for the entire document -.. _FFmpeg: http://ffmpeg.org +.. _FFmpeg: https://ffmpeg.org .. _Vorbis: http://www.vorbis.com -.. _Theora: http://www.theora.org +.. _Theora: https://www.theora.org diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index c9f682f17..f124b526f 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -21,4 +21,6 @@ few chapters to familiarise yourself with the new interface. tour files-and-directories starting-dialog - + tables + record-types + record-filters diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 645c18453..83b7aae27 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -3,7 +3,7 @@ A Tour through OpenMW CS: making a magic ring In this first chapter we will create a mod that adds a new ring with a simple enchantment to the game. The ring will give its wearer a permanent Night Vision -effect while being worn. You do not need previous Morrowind modding experience, +effect while being worn. You do not need previous Morrowind modding experience, but you should be familiar with the game itself. There will be no scripting necessary, we can achieve everything using just what the base game offers out of the box. Before continuing make sure that OpenMW is properly @@ -133,7 +133,7 @@ the filter directly into the filter field rather than the name of an existing filter. To signify that we are using an instant filter the have to use `!` as the first character. Type the following into the field: -.. code:: +.. code:: !string("id", ".*ring.*") @@ -217,20 +217,20 @@ actually modify the contents of the game. Adding to an NPC ================ -The simplest way is probably to add it to the inventory of a shopkeeper. +The simplest way is probably to add it to the inventory of a shopkeeper. An obvious candidate is Arrille in Seyda Neen - he's quick to find in a new game and he's easy to find in the CS as his name comes early alphabetically. .. figure:: _static/images/chapter-1/Ring_to_Arrille.png :alt: Putting the ring into Arrille's inventory - -Open the CS and open the *Objects* table (*World* → *Objects*). + +Open the CS and open the *Objects* table (*World* → *Objects*). Scroll down to Arrille, or use a filter like !string("ID","arrille"). -Open another pane to edit him - either right click and select edit or use the +Open another pane to edit him - either right click and select edit or use the shortcut (default is shift double-click). Scroll down to the inventory section -and right click to add a new row. Type in the id of the ring (or find it in the -object pane, and drag and drop). Set the number of rings for him to stock - with +and right click to add a new row. Type in the id of the ring (or find it in the +object pane, and drag and drop). Set the number of rings for him to stock - with a negative number indicating that he will restock again to maintain that level. However, it's an attractive item, so he will probably wear it rather than sell it. @@ -241,7 +241,7 @@ Fargoth to give it to the player in exchange for his healing ring. .. figure:: _static/images/chapter-1/Ring_to_Fargoth_1.png :alt: Editing Fargoth to give ring to player - + Open the *Topicinfo* Table (*Characters* → *Topic Infos*). Use a filter !string(Topic,ring) and select the row with a response starting with "You found it!". Edit the record, firstly by adding a bit more to the response, then by adding a line to the script @@ -297,7 +297,7 @@ Placing in plain sight ===================== Let's hide the Ring of Night vision in the cabin of the [Ancient Shipwreck] -(http://en.uesp.net/wiki/Morrowind:Ancient_Shipwreck), a derelict vessel +(https://en.uesp.net/wiki/Morrowind:Ancient_Shipwreck), a derelict vessel southeast of Dagon Fel. Open the list of Cells (*World* → *Cells*) and find "Ancient Shipwreck, Cabin". @@ -333,8 +333,8 @@ This is probably a suitable place to start talking about how navigation differs in vanilla Morrowind. There is advice in Scripting for Dummies, the definitive manual for Morrowind Scripting: -"If you give your scripts a common tag, that will make it easier to jump between the -different scripts of your project, e.g. start every script name with AA_Scriptname +"If you give your scripts a common tag, that will make it easier to jump between the +different scripts of your project, e.g. start every script name with AA_Scriptname this will put them right at the beginning of the list and keep them neatly together." This is valid for the rather poorer navigation facilities there, but it's not sensible for @@ -359,12 +359,12 @@ the base game. "Modified" status will cover items from the base game which have been modified in this addon. -Click on the top of the column to toggle between ascending and descending order - thus between "Added" -and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically +Click on the top of the column to toggle between ascending and descending order - thus between "Added" +and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically on a different column. - + Checking your new addon ======================= @@ -372,4 +372,4 @@ Launch OpenMW and in the launcher under *Data Files* check your addon, if it's n already checked. Load a game and make your way to Seyda Neen - or start a new game. Check whether Arrille has one (or more) for sale, and whether Fargoth give you one -when you return his healing ring. \ No newline at end of file +when you return his healing ring. diff --git a/docs/source/reference/modding/convert_bump_mapped_mods.rst b/docs/source/reference/modding/convert_bump_mapped_mods.rst index 71ac29468..421fa67a7 100644 --- a/docs/source/reference/modding/convert_bump_mapped_mods.rst +++ b/docs/source/reference/modding/convert_bump_mapped_mods.rst @@ -20,9 +20,9 @@ General introduction to normal map conversion :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most (in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. +This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most (in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. -*Note:* The conversion made in the `Converting Apel's Various Things - Sacks`_-part of this tutorial require the use of the application NifSkope. There are binaries available for Windows, but not for Mac or Linux. Reports say that NifSkope versions 1.X will compile on Linux as long as you have Qt packages installed, while the later 2.X versions will not compile. +*Note:* The conversion made in the `Converting Apel's Various Things - Sacks`_-part of this tutorial require the use of the application NifSkope_. *Another note:* I will use the terms bump mapping and normal mapping simultaneously. Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, but bump mapping isn't necessarily normal mapping. There are several techniques for bump mapping, and normal mapping is the most common one today. @@ -160,8 +160,6 @@ Converting Apel's Various Things - Sacks In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) files so that the normal maps could be loaded in Morrowind with MCP. We ignored those model files since they are not needed with OpenMW. In this tutorial however, we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. -Before we begin, you need to know that unless you want to build the NifSkope application from source yourself, you will be needing a Windows OS to do this part, since the application only has binaries available for Windows. - Tutorial - MCP, Part 2 ********************** @@ -179,24 +177,24 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions – #. Remove all these tags by selecting them one at a time and press right click>Block>Remove Branch. (Ctrl-Del) #. Repeat this on all the affected models. #. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here. -#. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question. +#. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question. #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. Finally, `we are done`_! Since these models have one or two textures applied to them, the fix was not that time-consuming. It gets worse when you have to fix a model that uses loads of textures. The principle is the same, it just requires more manual work which is annoying and takes time. -.. _`Netch Bump mapped`: http://www.nexusmods.com/morrowind/mods/42851/? -.. _`Hlaalu Bump mapped`: http://www.nexusmods.com/morrowind/mods/42396/? +.. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? +.. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? .. _`On the Rocks`: http://mw.modhistory.com/download-44-14107 .. _`texture modding`: https://wiki.openmw.org/index.php?title=TextureModding -.. _`MGE XE`: http://www.nexusmods.com/morrowind/mods/26348/? -.. _PeterBitt: http://www.nexusmods.com/morrowind/users/4381248/? -.. _`PBR Scamp Replacer`: http://www.nexusmods.com/morrowind/mods/44314/? +.. _`MGE XE`: https://www.nexusmods.com/morrowind/mods/26348/? +.. _PeterBitt: https://www.nexusmods.com/morrowind/users/4381248/? +.. _`PBR Scamp Replacer`: https://www.nexusmods.com/morrowind/mods/44314/? .. _settings.cfg: https://wiki.openmw.org/index.php?title=Settings .. _`Multiple data folders`: https://wiki.openmw.org/index.php?title=Mod_installation -.. _`Various Things - Sacks`: http://www.nexusmods.com/morrowind/mods/42558/? -.. _Lead: http://imgur.com/bwpcYlc -.. _NifSkope: http://niftools.sourceforge.net/wiki/NifSkope -.. _Blocks: http://imgur.com/VmQC0WG -.. _`no longer have shiny models`: http://imgur.com/vu1k7n1 -.. _`we are done`: http://imgur.com/yyZxlTw +.. _`Various Things - Sacks`: https://www.nexusmods.com/morrowind/mods/42558/? +.. _Lead: https://imgur.com/bwpcYlc +.. _NifSkope: https://wiki.openmw.org/index.php?title=Tools#NifSkope +.. _Blocks: https://imgur.com/VmQC0WG +.. _`no longer have shiny models`: https://imgur.com/vu1k7n1 +.. _`we are done`: https://imgur.com/yyZxlTw diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 5f01b12d9..80d01c27f 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -15,7 +15,7 @@ Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Th - To replace the primary "Magic Cards" font: - #. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). + #. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). #. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation. #. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well. #. If desired, you can now delete the original ``Magic_Cards.*`` files from your Data Files/Fonts directory. @@ -74,4 +74,4 @@ Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Th Bitmap fonts ------------ -Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. \ No newline at end of file +Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 45ac81d64..416f1bc1a 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -90,12 +90,26 @@ difficulty This setting adjusts the difficulty of the game and is intended to be in the range -100 to 100 inclusive. Given the default game setting for fDifficultyMult of 5.0, a value of -100 results in the player taking 80% of the usual damage, doing 6 times the normal damage. -A value of 100 results in the player taking 6 times as much damage, but inflicting only 80% of the usual damage. -Values less than -500 will result in the player receiving no damage, -and values greater than 500 will result in the player inflicting no damage. +A value of 100 results in the player taking 6 times as much damage, while inflicting only 80% of the usual damage. +Values below -500 will result in the player receiving no damage, +and values above 500 will result in the player inflicting no damage. This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu. +classic reflect absorb attribute behavior +----------------------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If this setting is true, "Absorb Attribute" spells which were reflected by the target are not "mirrored", +and the caster will absorb their own attribute resulting in no effect on both the caster and the target. +This makes the gameplay as a mage easier, but these spells become imbalanced. +This is how the original Morrowind behaves. + +This setting can only be configured by editing the settings configuration file. + show effect duration -------------------- @@ -108,6 +122,18 @@ The remaining duration is displayed in the tooltip by hovering over the magical This setting can only be configured by editing the settings configuration file. +enchanted weapons are magical +----------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +Makes enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. +This is how original Morrowind behaves. + +This setting can only be configured by editing the settings configuration file. + prevent merchant equipping -------------------------- @@ -131,3 +157,16 @@ Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. This setting can only be configured by editing the settings configuration file. + +use additional anim sources +--------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow to load additional animation sources when enabled. +For example, if the main animation mesh has name Meshes/x.nif, an engine will load all KF-files from Animations/x folder and its child folders. +Can be useful if you want to use several animation replacers without merging them. +Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/saves.rst b/docs/source/reference/modding/settings/saves.rst index 777404ebf..fdb25fb79 100644 --- a/docs/source/reference/modding/settings/saves.rst +++ b/docs/source/reference/modding/settings/saves.rst @@ -5,7 +5,7 @@ character --------- :Type: string -:Range: +:Range: :Default: "" This contains the default character for the Load Game menu and is automatically updated when a different character is selected. @@ -32,3 +32,14 @@ This setting determines whether the amount of the time the player has spent play for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, but does not include time spent with the game window minimized. This setting can only be configured by editing the settings configuration file. + +max quicksaves +---------- + +:Type: integer +:Range: >0 +:Default: 1 + +This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave. + +This setting can only be configured by editing the settings configuration file. diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp index 5decaf1eb..e389bc981 100644 --- a/extern/oics/ICSInputControlSystem_mouse.cpp +++ b/extern/oics/ICSInputControlSystem_mouse.cpp @@ -268,11 +268,11 @@ namespace ICS ctrl->setIgnoreAutoReverse(true); if(mouseBinderItem.direction == Control::INCREASE) { - ctrl->setValue( float( (evt.state.Z.abs) / float(evt.state.width?) ) ); + ctrl->setValue( float( (evt.state.Z.abs) / float(evt.state.¿width?) ) ); } else if(mouseBinderItem.direction == Control::DECREASE) { - ctrl->setValue( float( (1 - evt.state.Z.abs) / float(evt.state.width?) ) ); + ctrl->setValue( float( (1 - evt.state.Z.abs) / float(evt.state.¿width?) ) ); } }*/ } diff --git a/extern/oics/tinystr.cpp b/extern/oics/tinystr.cpp index 681250714..635e84b5f 100644 --- a/extern/oics/tinystr.cpp +++ b/extern/oics/tinystr.cpp @@ -23,7 +23,7 @@ distribution. */ /* - * THIS FILE WAS ALTERED BY Tyge Lvset, 7. April 2005. + * THIS FILE WAS ALTERED BY Tyge Løvset, 7. April 2005. */ diff --git a/extern/oics/tinyxmlparser.cpp b/extern/oics/tinyxmlparser.cpp index 253cd93ff..d5bda8fee 100644 --- a/extern/oics/tinyxmlparser.cpp +++ b/extern/oics/tinyxmlparser.cpp @@ -2,23 +2,23 @@ www.sourceforge.net/projects/tinyxml Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages arising from the use of this software. -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: -1. The origin of this software must not be misrepresented; you must +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. -2. Altered source versions must be plainly marked as such, and +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -3. This notice may not be removed or altered from any source +3. This notice may not be removed or altered from any source distribution. */ @@ -39,8 +39,8 @@ distribution. // Note tha "PutString" hardcodes the same list. This // is less flexible than it appears. Changing the entries -// or order will break putstring. -TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = { { "&", 5, '&' }, { "<", 4, '<' }, @@ -50,20 +50,20 @@ TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = }; // Bunch of unicode info at: -// http://www.unicode.org/faq/utf_bom.html +// https://www.unicode.org/faq/utf_bom.html // Including the basic of this table, which determines the #bytes in the // sequence from the lead byte. 1 placed for invalid sequences -- // although the result will be junk, pass it through as much as possible. -// Beware of the non-characters in UTF-8: +// Beware of the non-characters in UTF-8: // ef bb bf (Microsoft "lead bytes") // ef bf be -// ef bf bf +// ef bf bf const unsigned char TIXML_UTF_LEAD_0 = 0xefU; const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; -const int TiXmlBase::utf8ByteTable[256] = +const int TiXmlBase::utf8ByteTable[256] = { // 0 1 2 3 4 5 6 7 8 9 a b c d e f 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 @@ -75,9 +75,9 @@ const int TiXmlBase::utf8ByteTable[256] = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte @@ -91,7 +91,7 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng const unsigned long BYTE_MARK = 0x80; const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - if (input < 0x80) + if (input < 0x80) *length = 1; else if ( input < 0x800 ) *length = 2; @@ -105,22 +105,22 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng output += *length; // Scary scary fall throughs. - switch (*length) + switch (*length) { case 4: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; case 3: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; case 2: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; case 1: - --output; + --output; *output = (char)(input | FIRST_BYTE_MARK[*length]); } } @@ -130,7 +130,7 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng { // This will only work for low-ascii, everything else is assumed to be a valid // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very + // to figure out alhabetical vs. not across encoding. So take a very // conservative approach. // if ( encoding == TIXML_ENCODING_UTF8 ) @@ -151,7 +151,7 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng { // This will only work for low-ascii, everything else is assumed to be a valid // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very + // to figure out alhabetical vs. not across encoding. So take a very // conservative approach. // if ( encoding == TIXML_ENCODING_UTF8 ) @@ -224,7 +224,7 @@ void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) case '\r': // bump down to the next line ++row; - col = 0; + col = 0; // Eat the character ++p; @@ -266,11 +266,11 @@ void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) // In these cases, don't advance the column. These are // 0-width spaces. if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) - p += 3; + p += 3; else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) - p += 3; + p += 3; else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) - p += 3; + p += 3; else { p +=3; ++col; } // A normal character. } @@ -322,10 +322,10 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) while ( *p ) { const unsigned char* pU = (const unsigned char*)p; - + // Skip the stupid Microsoft UTF-8 Byte order marks if ( *(pU+0)==TIXML_UTF_LEAD_0 - && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) { p += 3; @@ -413,12 +413,12 @@ const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncodi // After that, they can be letters, underscores, numbers, // hyphens, or colons. (Colons are valid ony for namespaces, // but tinyxml can't tell namespaces from names.) - if ( p && *p + if ( p && *p && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) { const char* start = p; while( p && *p - && ( IsAlphaNum( (unsigned char ) *p, encoding ) + && ( IsAlphaNum( (unsigned char ) *p, encoding ) || *p == '_' || *p == '-' || *p == '.' @@ -469,7 +469,7 @@ const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXml ucs += mult * (*q - 'a' + 10); else if ( *q >= 'A' && *q <= 'F' ) ucs += mult * (*q - 'A' + 10 ); - else + else return 0; mult *= 16; --q; @@ -492,7 +492,7 @@ const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXml { if ( *q >= '0' && *q <= '9' ) ucs += mult * (*q - '0'); - else + else return 0; mult *= 10; --q; @@ -571,10 +571,10 @@ bool TiXmlBase::StringEqual( const char* p, return false; } -const char* TiXmlBase::ReadText( const char* p, - TIXML_STRING * text, - bool trimWhiteSpace, - const char* endTag, +const char* TiXmlBase::ReadText( const char* p, + TIXML_STRING * text, + bool trimWhiteSpace, + const char* endTag, bool caseInsensitive, TiXmlEncoding encoding ) { @@ -631,7 +631,7 @@ const char* TiXmlBase::ReadText( const char* p, } } } - if ( p ) + if ( p ) p += strlen( endTag ); return p; } @@ -647,7 +647,7 @@ void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) // This "pre-streaming" will never read the closing ">" so the // sub-tag can orient itself. - if ( !StreamTo( in, '<', tag ) ) + if ( !StreamTo( in, '<', tag ) ) { SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); return; @@ -669,7 +669,7 @@ void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) if ( in->good() ) { - // We now have something we presume to be a node of + // We now have something we presume to be a node of // some sort. Identify it, and call the node to // continue streaming. TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); @@ -778,7 +778,7 @@ const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiX encoding = TIXML_ENCODING_UTF8; else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice - else + else encoding = TIXML_ENCODING_LEGACY; } @@ -796,7 +796,7 @@ const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiX } void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) -{ +{ // The first error in a chain is more accurate - don't set again! if ( error ) return; @@ -833,7 +833,7 @@ TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) return 0; } - // What is this thing? + // What is this thing? // - Elements start with a letter or underscore, but xml is reserved. // - Comments: - + + + + + diff --git a/files/opencs/GMST.png b/files/opencs/GMST.png deleted file mode 100644 index f24620288..000000000 Binary files a/files/opencs/GMST.png and /dev/null differ diff --git a/files/opencs/LandTexture.png b/files/opencs/LandTexture.png deleted file mode 100644 index 84f729098..000000000 Binary files a/files/opencs/LandTexture.png and /dev/null differ diff --git a/files/opencs/Lightbulb-48.png b/files/opencs/Lightbulb-48.png deleted file mode 100644 index c7a45528e..000000000 Binary files a/files/opencs/Lightbulb-48.png and /dev/null differ diff --git a/files/opencs/Moon-48.png b/files/opencs/Moon-48.png deleted file mode 100644 index 016b030f2..000000000 Binary files a/files/opencs/Moon-48.png and /dev/null differ diff --git a/files/opencs/PathGrid.png b/files/opencs/PathGrid.png deleted file mode 100644 index 23b6b84d7..000000000 Binary files a/files/opencs/PathGrid.png and /dev/null differ diff --git a/files/opencs/Sun-48.png b/files/opencs/Sun-48.png deleted file mode 100644 index dbdede349..000000000 Binary files a/files/opencs/Sun-48.png and /dev/null differ diff --git a/files/opencs/activator.png b/files/opencs/activator.png index 32cc6f8a3..ded6ab835 100644 Binary files a/files/opencs/activator.png and b/files/opencs/activator.png differ diff --git a/files/opencs/add.png b/files/opencs/add.png deleted file mode 100644 index 3f1347e24..000000000 Binary files a/files/opencs/add.png and /dev/null differ diff --git a/files/opencs/added.png b/files/opencs/added.png deleted file mode 100644 index ddd9c2108..000000000 Binary files a/files/opencs/added.png and /dev/null differ diff --git a/files/opencs/apparatus.png b/files/opencs/apparatus.png index 3cef537e1..4e95397df 100644 Binary files a/files/opencs/apparatus.png and b/files/opencs/apparatus.png differ diff --git a/files/opencs/armor.png b/files/opencs/armor.png index fc534c7d1..6f5cc83c5 100644 Binary files a/files/opencs/armor.png and b/files/opencs/armor.png differ diff --git a/files/opencs/attribute.png b/files/opencs/attribute.png index 4aa5dc02e..c12456717 100644 Binary files a/files/opencs/attribute.png and b/files/opencs/attribute.png differ diff --git a/files/opencs/base.png b/files/opencs/base.png deleted file mode 100644 index 4398e2d68..000000000 Binary files a/files/opencs/base.png and /dev/null differ diff --git a/files/opencs/birthsign.png b/files/opencs/birthsign.png index 8192d2ebf..861dbd4d1 100644 Binary files a/files/opencs/birthsign.png and b/files/opencs/birthsign.png differ diff --git a/files/opencs/body-part.png b/files/opencs/body-part.png index 823e43712..8baec7202 100644 Binary files a/files/opencs/body-part.png and b/files/opencs/body-part.png differ diff --git a/files/opencs/book.png b/files/opencs/book.png index 9d7669bd7..fdecb1585 100644 Binary files a/files/opencs/book.png and b/files/opencs/book.png differ diff --git a/files/opencs/brush-circle.png b/files/opencs/brush-circle.png new file mode 100644 index 000000000..22e92c1c7 Binary files /dev/null and b/files/opencs/brush-circle.png differ diff --git a/files/opencs/brush-custom.png b/files/opencs/brush-custom.png new file mode 100644 index 000000000..58c048550 Binary files /dev/null and b/files/opencs/brush-custom.png differ diff --git a/files/opencs/brush-point.png b/files/opencs/brush-point.png new file mode 100644 index 000000000..19b2354f8 Binary files /dev/null and b/files/opencs/brush-point.png differ diff --git a/files/opencs/brush-square.png b/files/opencs/brush-square.png new file mode 100644 index 000000000..08628772e Binary files /dev/null and b/files/opencs/brush-square.png differ diff --git a/files/opencs/camera-first-person.png b/files/opencs/camera-first-person.png new file mode 100644 index 000000000..b497198df Binary files /dev/null and b/files/opencs/camera-first-person.png differ diff --git a/files/opencs/camera-free.png b/files/opencs/camera-free.png new file mode 100644 index 000000000..d8e7ccae5 Binary files /dev/null and b/files/opencs/camera-free.png differ diff --git a/files/opencs/camera-orbit.png b/files/opencs/camera-orbit.png new file mode 100644 index 000000000..1aedf2847 Binary files /dev/null and b/files/opencs/camera-orbit.png differ diff --git a/files/opencs/cell.png b/files/opencs/cell.png index c4f00c1f0..9127dd5e5 100644 Binary files a/files/opencs/cell.png and b/files/opencs/cell.png differ diff --git a/files/opencs/class.png b/files/opencs/class.png index 316380363..272f2630c 100644 Binary files a/files/opencs/class.png and b/files/opencs/class.png differ diff --git a/files/opencs/clothing.png b/files/opencs/clothing.png index 88c9b6ab8..e42988e74 100644 Binary files a/files/opencs/clothing.png and b/files/opencs/clothing.png differ diff --git a/files/opencs/container.png b/files/opencs/container.png index 2a6ed01eb..336a05dbf 100644 Binary files a/files/opencs/container.png and b/files/opencs/container.png differ diff --git a/files/opencs/creature.png b/files/opencs/creature.png index 99cf9c87c..003684dca 100644 Binary files a/files/opencs/creature.png and b/files/opencs/creature.png differ diff --git a/files/opencs/dialogoue-info.png b/files/opencs/dialogoue-info.png deleted file mode 100644 index f6743d43c..000000000 Binary files a/files/opencs/dialogoue-info.png and /dev/null differ diff --git a/files/opencs/dialogoue-journal.png b/files/opencs/dialogoue-journal.png deleted file mode 100644 index b6a95c538..000000000 Binary files a/files/opencs/dialogoue-journal.png and /dev/null differ diff --git a/files/opencs/dialogoue-regular.png b/files/opencs/dialogoue-regular.png deleted file mode 100644 index f9b8d252d..000000000 Binary files a/files/opencs/dialogoue-regular.png and /dev/null differ diff --git a/files/opencs/dialogue-greeting.png b/files/opencs/dialogue-greeting.png index a35e1fe6d..de6b22b42 100644 Binary files a/files/opencs/dialogue-greeting.png and b/files/opencs/dialogue-greeting.png differ diff --git a/files/opencs/dialogue-info.png b/files/opencs/dialogue-info.png new file mode 100644 index 000000000..6242eddf4 Binary files /dev/null and b/files/opencs/dialogue-info.png differ diff --git a/files/opencs/dialogue-journal.png b/files/opencs/dialogue-journal.png new file mode 100644 index 000000000..086cb8a42 Binary files /dev/null and b/files/opencs/dialogue-journal.png differ diff --git a/files/opencs/dialogue-persuasion.png b/files/opencs/dialogue-persuasion.png index 5bc5d6113..3138862c8 100644 Binary files a/files/opencs/dialogue-persuasion.png and b/files/opencs/dialogue-persuasion.png differ diff --git a/files/opencs/dialogue-regular.png b/files/opencs/dialogue-regular.png new file mode 100644 index 000000000..933afc595 Binary files /dev/null and b/files/opencs/dialogue-regular.png differ diff --git a/files/opencs/dialogue-speech.png b/files/opencs/dialogue-speech.png deleted file mode 100644 index 11eb9f1ca..000000000 Binary files a/files/opencs/dialogue-speech.png and /dev/null differ diff --git a/files/opencs/dialogue-topic-infos.png b/files/opencs/dialogue-topic-infos.png new file mode 100644 index 000000000..6242eddf4 Binary files /dev/null and b/files/opencs/dialogue-topic-infos.png differ diff --git a/files/opencs/dialogue-topics.png b/files/opencs/dialogue-topics.png new file mode 100644 index 000000000..caa6d7e7c Binary files /dev/null and b/files/opencs/dialogue-topics.png differ diff --git a/files/opencs/dialogue-voice.png b/files/opencs/dialogue-voice.png new file mode 100644 index 000000000..1d67745e5 Binary files /dev/null and b/files/opencs/dialogue-voice.png differ diff --git a/files/opencs/door.png b/files/opencs/door.png index aa48858ef..a1a823ecd 100644 Binary files a/files/opencs/door.png and b/files/opencs/door.png differ diff --git a/files/opencs/editing-instance.png b/files/opencs/editing-instance.png new file mode 100644 index 000000000..7349e8f66 Binary files /dev/null and b/files/opencs/editing-instance.png differ diff --git a/files/opencs/editing-pathgrid.png b/files/opencs/editing-pathgrid.png new file mode 100644 index 000000000..0ec024cad Binary files /dev/null and b/files/opencs/editing-pathgrid.png differ diff --git a/files/opencs/editing-terrain-movement.png b/files/opencs/editing-terrain-movement.png new file mode 100644 index 000000000..40777334b Binary files /dev/null and b/files/opencs/editing-terrain-movement.png differ diff --git a/files/opencs/editing-terrain-shape.png b/files/opencs/editing-terrain-shape.png new file mode 100644 index 000000000..a11bd95d5 Binary files /dev/null and b/files/opencs/editing-terrain-shape.png differ diff --git a/files/opencs/editing-terrain-texture.png b/files/opencs/editing-terrain-texture.png new file mode 100644 index 000000000..4a88353ee Binary files /dev/null and b/files/opencs/editing-terrain-texture.png differ diff --git a/files/opencs/editing-terrain-vertex-paint.png b/files/opencs/editing-terrain-vertex-paint.png new file mode 100644 index 000000000..2b3f0beac Binary files /dev/null and b/files/opencs/editing-terrain-vertex-paint.png differ diff --git a/files/opencs/enchantment.png b/files/opencs/enchantment.png index c90fb27ce..9bd54b8f0 100644 Binary files a/files/opencs/enchantment.png and b/files/opencs/enchantment.png differ diff --git a/files/opencs/eyeballdude.png b/files/opencs/eyeballdude.png deleted file mode 100644 index c782880f3..000000000 Binary files a/files/opencs/eyeballdude.png and /dev/null differ diff --git a/files/opencs/faction.png b/files/opencs/faction.png index 8ac1f5200..b58756bdc 100644 Binary files a/files/opencs/faction.png and b/files/opencs/faction.png differ diff --git a/files/opencs/filter.png b/files/opencs/filter.png index 94a57ecd9..55f442377 100644 Binary files a/files/opencs/filter.png and b/files/opencs/filter.png differ diff --git a/files/opencs/flying-eye.png b/files/opencs/flying-eye.png deleted file mode 100644 index e379d7fad..000000000 Binary files a/files/opencs/flying-eye.png and /dev/null differ diff --git a/files/opencs/global-variable.png b/files/opencs/global-variable.png new file mode 100644 index 000000000..e1642ac35 Binary files /dev/null and b/files/opencs/global-variable.png differ diff --git a/files/opencs/globvar.png b/files/opencs/globvar.png deleted file mode 100644 index 646145f0f..000000000 Binary files a/files/opencs/globvar.png and /dev/null differ diff --git a/files/opencs/gmst.png b/files/opencs/gmst.png new file mode 100644 index 000000000..2605fb4b9 Binary files /dev/null and b/files/opencs/gmst.png differ diff --git a/files/opencs/go-next.png b/files/opencs/go-next.png deleted file mode 100644 index 6ef8de76e..000000000 Binary files a/files/opencs/go-next.png and /dev/null differ diff --git a/files/opencs/go-previous.png b/files/opencs/go-previous.png deleted file mode 100644 index 659cd90d7..000000000 Binary files a/files/opencs/go-previous.png and /dev/null differ diff --git a/files/opencs/ingredient.png b/files/opencs/ingredient.png index 564a93047..f31e6f581 100644 Binary files a/files/opencs/ingredient.png and b/files/opencs/ingredient.png differ diff --git a/files/opencs/journal-topic-infos.png b/files/opencs/journal-topic-infos.png new file mode 100644 index 000000000..4cc446489 Binary files /dev/null and b/files/opencs/journal-topic-infos.png differ diff --git a/files/opencs/journal-topics.png b/files/opencs/journal-topics.png new file mode 100644 index 000000000..d4e58a288 Binary files /dev/null and b/files/opencs/journal-topics.png differ diff --git a/files/opencs/land-heightmap.png b/files/opencs/land-heightmap.png new file mode 100644 index 000000000..5b460a002 Binary files /dev/null and b/files/opencs/land-heightmap.png differ diff --git a/files/opencs/land-texture.png b/files/opencs/land-texture.png new file mode 100644 index 000000000..a96c4bf8c Binary files /dev/null and b/files/opencs/land-texture.png differ diff --git a/files/opencs/land.png b/files/opencs/land.png deleted file mode 100644 index 20dd321dd..000000000 Binary files a/files/opencs/land.png and /dev/null differ diff --git a/files/opencs/landpaint.png b/files/opencs/landpaint.png deleted file mode 100644 index 711c0d8f5..000000000 Binary files a/files/opencs/landpaint.png and /dev/null differ diff --git a/files/opencs/leveled-creature.png b/files/opencs/leveled-creature.png index ad4a7c6f8..e6cb1f54c 100644 Binary files a/files/opencs/leveled-creature.png and b/files/opencs/leveled-creature.png differ diff --git a/files/opencs/leveled-item.png b/files/opencs/leveled-item.png old mode 100755 new mode 100644 index 7b8e68e60..3c819c56d Binary files a/files/opencs/leveled-item.png and b/files/opencs/leveled-item.png differ diff --git a/files/opencs/light.png b/files/opencs/light.png index 2765ef1d3..55d03bcd1 100644 Binary files a/files/opencs/light.png and b/files/opencs/light.png differ diff --git a/files/opencs/lighting-lamp.png b/files/opencs/lighting-lamp.png new file mode 100644 index 000000000..c86517aa5 Binary files /dev/null and b/files/opencs/lighting-lamp.png differ diff --git a/files/opencs/lighting-moon.png b/files/opencs/lighting-moon.png new file mode 100644 index 000000000..36a6e9b5b Binary files /dev/null and b/files/opencs/lighting-moon.png differ diff --git a/files/opencs/lighting-sun.png b/files/opencs/lighting-sun.png new file mode 100644 index 000000000..a54d0ab12 Binary files /dev/null and b/files/opencs/lighting-sun.png differ diff --git a/files/opencs/list-added.png b/files/opencs/list-added.png new file mode 100644 index 000000000..4da265983 Binary files /dev/null and b/files/opencs/list-added.png differ diff --git a/files/opencs/list-base.png b/files/opencs/list-base.png new file mode 100644 index 000000000..336d4c59c Binary files /dev/null and b/files/opencs/list-base.png differ diff --git a/files/opencs/list-modified.png b/files/opencs/list-modified.png new file mode 100644 index 000000000..8b269a31d Binary files /dev/null and b/files/opencs/list-modified.png differ diff --git a/files/opencs/list-removed.png b/files/opencs/list-removed.png new file mode 100644 index 000000000..618a202bb Binary files /dev/null and b/files/opencs/list-removed.png differ diff --git a/files/opencs/lockpick.png b/files/opencs/lockpick.png index d9bd27f5e..7b1865f50 100644 Binary files a/files/opencs/lockpick.png and b/files/opencs/lockpick.png differ diff --git a/files/opencs/magic-effect.png b/files/opencs/magic-effect.png index e672ffccb..4901724c5 100644 Binary files a/files/opencs/magic-effect.png and b/files/opencs/magic-effect.png differ diff --git a/files/opencs/miscellaneous.png b/files/opencs/miscellaneous.png index 744bcd9db..b21f6e214 100644 Binary files a/files/opencs/miscellaneous.png and b/files/opencs/miscellaneous.png differ diff --git a/files/opencs/modified.png b/files/opencs/modified.png deleted file mode 100644 index 39bd182ac..000000000 Binary files a/files/opencs/modified.png and /dev/null differ diff --git a/files/opencs/npc.png b/files/opencs/npc.png index 7a07f26df..5b5b199be 100644 Binary files a/files/opencs/npc.png and b/files/opencs/npc.png differ diff --git a/files/opencs/orbit2.png b/files/opencs/orbit2.png deleted file mode 100644 index aa5c541d3..000000000 Binary files a/files/opencs/orbit2.png and /dev/null differ diff --git a/files/opencs/pathgrid.png b/files/opencs/pathgrid.png new file mode 100644 index 000000000..710ff1357 Binary files /dev/null and b/files/opencs/pathgrid.png differ diff --git a/files/opencs/potion.png b/files/opencs/potion.png index 678f61fbf..cb173bb9e 100644 Binary files a/files/opencs/potion.png and b/files/opencs/potion.png differ diff --git a/files/opencs/probe.png b/files/opencs/probe.png index 01536186d..2e405d365 100644 Binary files a/files/opencs/probe.png and b/files/opencs/probe.png differ diff --git a/files/opencs/race.png b/files/opencs/race.png index 94a2de696..aeed2fdf3 100644 Binary files a/files/opencs/race.png and b/files/opencs/race.png differ diff --git a/files/opencs/record-add.png b/files/opencs/record-add.png new file mode 100644 index 000000000..d477f1946 Binary files /dev/null and b/files/opencs/record-add.png differ diff --git a/files/opencs/record-clone.png b/files/opencs/record-clone.png new file mode 100644 index 000000000..262a67a42 Binary files /dev/null and b/files/opencs/record-clone.png differ diff --git a/files/opencs/record-delete.png b/files/opencs/record-delete.png new file mode 100644 index 000000000..817988a5d Binary files /dev/null and b/files/opencs/record-delete.png differ diff --git a/files/opencs/record-next.png b/files/opencs/record-next.png new file mode 100644 index 000000000..76866e473 Binary files /dev/null and b/files/opencs/record-next.png differ diff --git a/files/opencs/record-preview.png b/files/opencs/record-preview.png new file mode 100644 index 000000000..e3adb6ede Binary files /dev/null and b/files/opencs/record-preview.png differ diff --git a/files/opencs/record-previous.png b/files/opencs/record-previous.png new file mode 100644 index 000000000..2009d84e0 Binary files /dev/null and b/files/opencs/record-previous.png differ diff --git a/files/opencs/record-revert.png b/files/opencs/record-revert.png new file mode 100644 index 000000000..bd177ce65 Binary files /dev/null and b/files/opencs/record-revert.png differ diff --git a/files/opencs/region.png b/files/opencs/region.png new file mode 100644 index 000000000..a10089243 Binary files /dev/null and b/files/opencs/region.png differ diff --git a/files/opencs/removed.png b/files/opencs/removed.png deleted file mode 100644 index 2354bc743..000000000 Binary files a/files/opencs/removed.png and /dev/null differ diff --git a/files/opencs/repair.png b/files/opencs/repair.png index 6cf1c0aac..1b5a9ccc1 100644 Binary files a/files/opencs/repair.png and b/files/opencs/repair.png differ diff --git a/files/opencs/resources-icon.png b/files/opencs/resources-icon.png new file mode 100644 index 000000000..d84c90d5d Binary files /dev/null and b/files/opencs/resources-icon.png differ diff --git a/files/opencs/resources-mesh.png b/files/opencs/resources-mesh.png new file mode 100644 index 000000000..fdfa3528b Binary files /dev/null and b/files/opencs/resources-mesh.png differ diff --git a/files/opencs/resources-music.png b/files/opencs/resources-music.png new file mode 100644 index 000000000..53775109c Binary files /dev/null and b/files/opencs/resources-music.png differ diff --git a/files/opencs/resources-sound.png b/files/opencs/resources-sound.png new file mode 100644 index 000000000..86871611f Binary files /dev/null and b/files/opencs/resources-sound.png differ diff --git a/files/opencs/resources-texture.png b/files/opencs/resources-texture.png new file mode 100644 index 000000000..a96c4bf8c Binary files /dev/null and b/files/opencs/resources-texture.png differ diff --git a/files/opencs/resources-video.png b/files/opencs/resources-video.png new file mode 100644 index 000000000..d86bc6025 Binary files /dev/null and b/files/opencs/resources-video.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 7e798527c..0afd855f9 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -2,11 +2,11 @@ openmw-cs.png activator.png - added.png + list-added.png apparatus.png armor.png attribute.png - base.png + list-base.png birthsign.png body-part.png book.png @@ -15,55 +15,65 @@ clothing.png container.png creature.png - dialogoue-info.png - dialogoue-journal.png - dialogoue-regular.png + dialogue-info.png + dialogue-journal.png + dialogue-regular.png dialogue-greeting.png dialogue-persuasion.png - dialogue-speech.png + dialogue-voice.png + dialogue-topics.png + dialogue-topic-infos.png + journal-topic-infos.png + journal-topics.png door.png enchantment.png faction.png filter.png - globvar.png - GMST.png + global-variable.png + gmst.png Info.png ingredient.png - landpaint.png - land.png - LandTexture.png + land-heightmap.png + land-texture.png leveled-creature.png + leveled-item.png light.png lockpick.png magic-effect.png magicrabbit.png map.png miscellaneous.png - modified.png + list-modified.png npc.png - PathGrid.png + pathgrid.png potion.png probe.png race.png random-item.png random.png - removed.png + list-removed.png repair.png script.png skill.png - soundgen.png + sound-generator.png sound.png spell.png static.png weapon.png multitype.png - go-next.png - go-previous.png - edit-delete.png - edit-undo.png - edit-preview.png - edit-clone.png - add.png + record-next.png + record-previous.png + record-delete.png + record-revert.png + record-preview.png + record-clone.png + record-add.png + resources-icon.png + resources-mesh.png + resources-music.png + resources-sound.png + resources-texture.png + resources-video.png placeholder.png @@ -73,14 +83,14 @@ raster/startup/small/configure.png - Moon-48.png - Sun-48.png - Lightbulb-48.png - eyeballdude.png - flying-eye.png - orbit2.png - scene-play.png - scene-view-references.png + lighting-moon.png + lighting-sun.png + lighting-lamp.png + camera-first-person.png + camera-free.png + camera-orbit.png + run-game.png + scene-view-instance.png scene-view-terrain.png scene-view-water.png scene-view-pathgrid.png @@ -119,7 +129,7 @@ scene-view-status-31.png scene-exterior-arrows.png scene-exterior-borders.png - scene-exterior-marker.png + scene-exterior-markers.png scene-exterior-status-0.png scene-exterior-status-1.png scene-exterior-status-2.png @@ -128,5 +138,21 @@ scene-exterior-status-5.png scene-exterior-status-6.png scene-exterior-status-7.png + editing-instance.png + editing-pathgrid.png + editing-terrain-movement.png + editing-terrain-shape.png + editing-terrain-texture.png + editing-terrain-vertex-paint.png + transform-move.png + transform-rotate.png + transform-scale.png + selection-mode-cube.png + selection-mode-cube-corner.png + selection-mode-cube-sphere.png + brush-point.png + brush-square.png + brush-circle.png + brush-custom.png diff --git a/files/opencs/run-game.png b/files/opencs/run-game.png new file mode 100644 index 000000000..f5038654f Binary files /dev/null and b/files/opencs/run-game.png differ diff --git a/files/opencs/scene-exterior-arrows.png b/files/opencs/scene-exterior-arrows.png index f388255ad..661bb81ae 100644 Binary files a/files/opencs/scene-exterior-arrows.png and b/files/opencs/scene-exterior-arrows.png differ diff --git a/files/opencs/scene-exterior-borders.png b/files/opencs/scene-exterior-borders.png index 9711df62c..ec5040dc8 100644 Binary files a/files/opencs/scene-exterior-borders.png and b/files/opencs/scene-exterior-borders.png differ diff --git a/files/opencs/scene-exterior-marker.png b/files/opencs/scene-exterior-marker.png deleted file mode 100644 index a2de374b7..000000000 Binary files a/files/opencs/scene-exterior-marker.png and /dev/null differ diff --git a/files/opencs/scene-exterior-markers.png b/files/opencs/scene-exterior-markers.png index a2de374b7..6fffcbbcc 100644 Binary files a/files/opencs/scene-exterior-markers.png and b/files/opencs/scene-exterior-markers.png differ diff --git a/files/opencs/scene-exterior-status-0.png b/files/opencs/scene-exterior-status-0.png index f95de53e3..6fa47b439 100644 Binary files a/files/opencs/scene-exterior-status-0.png and b/files/opencs/scene-exterior-status-0.png differ diff --git a/files/opencs/scene-exterior-status-1.png b/files/opencs/scene-exterior-status-1.png index 09427b0c2..2e1ed0f65 100644 Binary files a/files/opencs/scene-exterior-status-1.png and b/files/opencs/scene-exterior-status-1.png differ diff --git a/files/opencs/scene-exterior-status-2.png b/files/opencs/scene-exterior-status-2.png index 086fe7b0a..8ccd356aa 100644 Binary files a/files/opencs/scene-exterior-status-2.png and b/files/opencs/scene-exterior-status-2.png differ diff --git a/files/opencs/scene-exterior-status-3.png b/files/opencs/scene-exterior-status-3.png index 68cdb07d2..70fdc4111 100644 Binary files a/files/opencs/scene-exterior-status-3.png and b/files/opencs/scene-exterior-status-3.png differ diff --git a/files/opencs/scene-exterior-status-4.png b/files/opencs/scene-exterior-status-4.png index c984989b5..2f2b907fc 100644 Binary files a/files/opencs/scene-exterior-status-4.png and b/files/opencs/scene-exterior-status-4.png differ diff --git a/files/opencs/scene-exterior-status-5.png b/files/opencs/scene-exterior-status-5.png index dee32f443..b294c1b15 100644 Binary files a/files/opencs/scene-exterior-status-5.png and b/files/opencs/scene-exterior-status-5.png differ diff --git a/files/opencs/scene-exterior-status-6.png b/files/opencs/scene-exterior-status-6.png index 877b005e4..872568b26 100644 Binary files a/files/opencs/scene-exterior-status-6.png and b/files/opencs/scene-exterior-status-6.png differ diff --git a/files/opencs/scene-exterior-status-7.png b/files/opencs/scene-exterior-status-7.png index 6a8afeff7..c19431025 100644 Binary files a/files/opencs/scene-exterior-status-7.png and b/files/opencs/scene-exterior-status-7.png differ diff --git a/files/opencs/scene-play.png b/files/opencs/scene-play.png deleted file mode 100644 index e2fabea7d..000000000 Binary files a/files/opencs/scene-play.png and /dev/null differ diff --git a/files/opencs/scene-view-fog.png b/files/opencs/scene-view-fog.png index 0ba2e69cf..65ea108ac 100644 Binary files a/files/opencs/scene-view-fog.png and b/files/opencs/scene-view-fog.png differ diff --git a/files/opencs/scene-view-instance.png b/files/opencs/scene-view-instance.png new file mode 100644 index 000000000..6f5e7cb2a Binary files /dev/null and b/files/opencs/scene-view-instance.png differ diff --git a/files/opencs/scene-view-pathgrid.png b/files/opencs/scene-view-pathgrid.png index 6586b882e..edb350b8b 100644 Binary files a/files/opencs/scene-view-pathgrid.png and b/files/opencs/scene-view-pathgrid.png differ diff --git a/files/opencs/scene-view-references.png b/files/opencs/scene-view-references.png deleted file mode 100644 index aa3bc73b2..000000000 Binary files a/files/opencs/scene-view-references.png and /dev/null differ diff --git a/files/opencs/scene-view-status-0.png b/files/opencs/scene-view-status-0.png index 2b2bb4d29..1906fd89c 100644 Binary files a/files/opencs/scene-view-status-0.png and b/files/opencs/scene-view-status-0.png differ diff --git a/files/opencs/scene-view-status-1.png b/files/opencs/scene-view-status-1.png index ba18877b9..6f5e7cb2a 100644 Binary files a/files/opencs/scene-view-status-1.png and b/files/opencs/scene-view-status-1.png differ diff --git a/files/opencs/scene-view-status-10.png b/files/opencs/scene-view-status-10.png index d6e23431e..1216c180f 100644 Binary files a/files/opencs/scene-view-status-10.png and b/files/opencs/scene-view-status-10.png differ diff --git a/files/opencs/scene-view-status-11.png b/files/opencs/scene-view-status-11.png index 21a77c533..dbe8276f0 100644 Binary files a/files/opencs/scene-view-status-11.png and b/files/opencs/scene-view-status-11.png differ diff --git a/files/opencs/scene-view-status-12.png b/files/opencs/scene-view-status-12.png index efe608d2e..ba6d1f323 100644 Binary files a/files/opencs/scene-view-status-12.png and b/files/opencs/scene-view-status-12.png differ diff --git a/files/opencs/scene-view-status-13.png b/files/opencs/scene-view-status-13.png index 2fe7d4025..3651cd609 100644 Binary files a/files/opencs/scene-view-status-13.png and b/files/opencs/scene-view-status-13.png differ diff --git a/files/opencs/scene-view-status-14.png b/files/opencs/scene-view-status-14.png index 56f77199f..4bddb7ebc 100644 Binary files a/files/opencs/scene-view-status-14.png and b/files/opencs/scene-view-status-14.png differ diff --git a/files/opencs/scene-view-status-15.png b/files/opencs/scene-view-status-15.png index 5dab523c6..bdd43407c 100644 Binary files a/files/opencs/scene-view-status-15.png and b/files/opencs/scene-view-status-15.png differ diff --git a/files/opencs/scene-view-status-16.png b/files/opencs/scene-view-status-16.png index 2ff5aba75..b0051eb12 100644 Binary files a/files/opencs/scene-view-status-16.png and b/files/opencs/scene-view-status-16.png differ diff --git a/files/opencs/scene-view-status-17.png b/files/opencs/scene-view-status-17.png index 1b30b6be1..def156032 100644 Binary files a/files/opencs/scene-view-status-17.png and b/files/opencs/scene-view-status-17.png differ diff --git a/files/opencs/scene-view-status-18.png b/files/opencs/scene-view-status-18.png index 363d396cb..7d9272687 100644 Binary files a/files/opencs/scene-view-status-18.png and b/files/opencs/scene-view-status-18.png differ diff --git a/files/opencs/scene-view-status-19.png b/files/opencs/scene-view-status-19.png index 31f90a5d3..17b75ce0e 100644 Binary files a/files/opencs/scene-view-status-19.png and b/files/opencs/scene-view-status-19.png differ diff --git a/files/opencs/scene-view-status-2.png b/files/opencs/scene-view-status-2.png index 06e4cd393..edb350b8b 100644 Binary files a/files/opencs/scene-view-status-2.png and b/files/opencs/scene-view-status-2.png differ diff --git a/files/opencs/scene-view-status-20.png b/files/opencs/scene-view-status-20.png index 5420953ed..bb950d94e 100644 Binary files a/files/opencs/scene-view-status-20.png and b/files/opencs/scene-view-status-20.png differ diff --git a/files/opencs/scene-view-status-21.png b/files/opencs/scene-view-status-21.png index 7344f3195..76ad0022c 100644 Binary files a/files/opencs/scene-view-status-21.png and b/files/opencs/scene-view-status-21.png differ diff --git a/files/opencs/scene-view-status-22.png b/files/opencs/scene-view-status-22.png index 99977a1cc..e33820a16 100644 Binary files a/files/opencs/scene-view-status-22.png and b/files/opencs/scene-view-status-22.png differ diff --git a/files/opencs/scene-view-status-23.png b/files/opencs/scene-view-status-23.png index 6c428e5a8..6c1fcc54f 100644 Binary files a/files/opencs/scene-view-status-23.png and b/files/opencs/scene-view-status-23.png differ diff --git a/files/opencs/scene-view-status-24.png b/files/opencs/scene-view-status-24.png index 903a4d5a4..057532b77 100644 Binary files a/files/opencs/scene-view-status-24.png and b/files/opencs/scene-view-status-24.png differ diff --git a/files/opencs/scene-view-status-25.png b/files/opencs/scene-view-status-25.png index dd7ad5458..061336c99 100644 Binary files a/files/opencs/scene-view-status-25.png and b/files/opencs/scene-view-status-25.png differ diff --git a/files/opencs/scene-view-status-26.png b/files/opencs/scene-view-status-26.png index fca0ed8e9..8fbcf68c6 100644 Binary files a/files/opencs/scene-view-status-26.png and b/files/opencs/scene-view-status-26.png differ diff --git a/files/opencs/scene-view-status-27.png b/files/opencs/scene-view-status-27.png index 1a888bff9..30eb053b9 100644 Binary files a/files/opencs/scene-view-status-27.png and b/files/opencs/scene-view-status-27.png differ diff --git a/files/opencs/scene-view-status-28.png b/files/opencs/scene-view-status-28.png index 5b11fda91..3a8f77457 100644 Binary files a/files/opencs/scene-view-status-28.png and b/files/opencs/scene-view-status-28.png differ diff --git a/files/opencs/scene-view-status-29.png b/files/opencs/scene-view-status-29.png index 211890638..eff610666 100644 Binary files a/files/opencs/scene-view-status-29.png and b/files/opencs/scene-view-status-29.png differ diff --git a/files/opencs/scene-view-status-3.png b/files/opencs/scene-view-status-3.png index 7d23409f7..ddf27c2db 100644 Binary files a/files/opencs/scene-view-status-3.png and b/files/opencs/scene-view-status-3.png differ diff --git a/files/opencs/scene-view-status-30.png b/files/opencs/scene-view-status-30.png index 825bc772c..63ce7d0d2 100644 Binary files a/files/opencs/scene-view-status-30.png and b/files/opencs/scene-view-status-30.png differ diff --git a/files/opencs/scene-view-status-31.png b/files/opencs/scene-view-status-31.png index 16d80af04..1906fd89c 100644 Binary files a/files/opencs/scene-view-status-31.png and b/files/opencs/scene-view-status-31.png differ diff --git a/files/opencs/scene-view-status-4.png b/files/opencs/scene-view-status-4.png index 82057190d..246c5aae9 100644 Binary files a/files/opencs/scene-view-status-4.png and b/files/opencs/scene-view-status-4.png differ diff --git a/files/opencs/scene-view-status-5.png b/files/opencs/scene-view-status-5.png index 79532bf0f..63d37f8be 100644 Binary files a/files/opencs/scene-view-status-5.png and b/files/opencs/scene-view-status-5.png differ diff --git a/files/opencs/scene-view-status-6.png b/files/opencs/scene-view-status-6.png index 2b1d38526..051aa64ae 100644 Binary files a/files/opencs/scene-view-status-6.png and b/files/opencs/scene-view-status-6.png differ diff --git a/files/opencs/scene-view-status-7.png b/files/opencs/scene-view-status-7.png index 991668330..6b2e5fdc1 100644 Binary files a/files/opencs/scene-view-status-7.png and b/files/opencs/scene-view-status-7.png differ diff --git a/files/opencs/scene-view-status-8.png b/files/opencs/scene-view-status-8.png index 406649aac..65ea108ac 100644 Binary files a/files/opencs/scene-view-status-8.png and b/files/opencs/scene-view-status-8.png differ diff --git a/files/opencs/scene-view-status-9.png b/files/opencs/scene-view-status-9.png index cf83a9f7c..72d0d9fb7 100644 Binary files a/files/opencs/scene-view-status-9.png and b/files/opencs/scene-view-status-9.png differ diff --git a/files/opencs/scene-view-terrain.png b/files/opencs/scene-view-terrain.png index 7ebbd7a9a..b0051eb12 100644 Binary files a/files/opencs/scene-view-terrain.png and b/files/opencs/scene-view-terrain.png differ diff --git a/files/opencs/scene-view-water.png b/files/opencs/scene-view-water.png index 0289b3c2c..246c5aae9 100644 Binary files a/files/opencs/scene-view-water.png and b/files/opencs/scene-view-water.png differ diff --git a/files/opencs/script.png b/files/opencs/script.png index 297da4021..29d622e19 100644 Binary files a/files/opencs/script.png and b/files/opencs/script.png differ diff --git a/files/opencs/selection-mode-cube-corner.png b/files/opencs/selection-mode-cube-corner.png new file mode 100644 index 000000000..b69ef1782 Binary files /dev/null and b/files/opencs/selection-mode-cube-corner.png differ diff --git a/files/opencs/selection-mode-cube-sphere.png b/files/opencs/selection-mode-cube-sphere.png new file mode 100644 index 000000000..cb5432eea Binary files /dev/null and b/files/opencs/selection-mode-cube-sphere.png differ diff --git a/files/opencs/selection-mode-cube.png b/files/opencs/selection-mode-cube.png new file mode 100644 index 000000000..1344a3fb1 Binary files /dev/null and b/files/opencs/selection-mode-cube.png differ diff --git a/files/opencs/skill.png b/files/opencs/skill.png index 418f4f35c..0ef7cb1cb 100644 Binary files a/files/opencs/skill.png and b/files/opencs/skill.png differ diff --git a/files/opencs/sound-generator.png b/files/opencs/sound-generator.png new file mode 100644 index 000000000..79833df9c Binary files /dev/null and b/files/opencs/sound-generator.png differ diff --git a/files/opencs/sound.png b/files/opencs/sound.png index b072acf76..86871611f 100644 Binary files a/files/opencs/sound.png and b/files/opencs/sound.png differ diff --git a/files/opencs/soundgen.png b/files/opencs/soundgen.png deleted file mode 100644 index 222fc4c7f..000000000 Binary files a/files/opencs/soundgen.png and /dev/null differ diff --git a/files/opencs/spell.png b/files/opencs/spell.png index 69c897180..5890ea751 100644 Binary files a/files/opencs/spell.png and b/files/opencs/spell.png differ diff --git a/files/opencs/static.png b/files/opencs/static.png index aedf2d30e..9f458e5d0 100644 Binary files a/files/opencs/static.png and b/files/opencs/static.png differ diff --git a/files/opencs/transform-move.png b/files/opencs/transform-move.png new file mode 100644 index 000000000..1e5bd573d Binary files /dev/null and b/files/opencs/transform-move.png differ diff --git a/files/opencs/transform-rotate.png b/files/opencs/transform-rotate.png new file mode 100644 index 000000000..b6c6bc58a Binary files /dev/null and b/files/opencs/transform-rotate.png differ diff --git a/files/opencs/transform-scale.png b/files/opencs/transform-scale.png new file mode 100644 index 000000000..c641259bd Binary files /dev/null and b/files/opencs/transform-scale.png differ diff --git a/files/opencs/weapon.png b/files/opencs/weapon.png index 3d4b53466..e2c1d3dc1 100644 Binary files a/files/opencs/weapon.png and b/files/opencs/weapon.png differ diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index 932c82ad7..e66fb2978 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -3,10 +3,10 @@ Copyright 2015 Alexandre Moine Copyright 2017 Bret Curtis --> - + org.openmw.desktop CC0-1.0 - GPL-3.0 and MIT + GPL-3.0 and MIT OpenMW Unofficial open source engine re-implementation of the game Morrowind @@ -20,21 +20,31 @@ Copyright 2017 Bret Curtis You will still need the original game data to play OpenMW.

        - - http://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png + https://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png The OpenMW launcher - http://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png + https://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png The Mournhold's plaza on OpenMW - http://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png + https://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png Vivec seen from Ebonheart on OpenMW + + http://wiki.openmw.org/images/a/a3/0.40_Screenshot-Balmora_3.png + Balmora at morning on OpenMW + + + Game + RolePlaying + + + + https://openmw.org https://bugs.openmw.org/ https://openmw.org/faq/ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f85c2a710..1390bf8c6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -7,8 +7,8 @@ # significance of each setting, interaction with other settings, hard # limits on value ranges and more information in general, please read # the detailed documentation at: -# -# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html +# +# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html # [Camera] @@ -90,6 +90,24 @@ pointers cache size = 40 # If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells distant terrain = false +[Fog] + +# If true, use extended fog parameters for distant terrain not controlled by +# viewing distance. If false, use the standard fog calculations. +use distant fog = false + +distant land fog start = 16384 + +distant land fog end = 40960 + +distant underwater fog start = -4096 + +distant underwater fog end = 2457.6 + +distant interior fog start = 0 + +distant interior fog end = 16384 + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). @@ -147,6 +165,9 @@ werewolf overlay = true color background owned = 0.15 0.0 0.0 1.0 color crosshair owned = 1.0 0.15 0.15 1.0 +# Controls whether Arrow keys, Movement keys, Tab/Shift-Tab and Spacebar/Enter/Activate may be used to navigate GUI buttons. +keyboard navigation = true + [HUD] # Displays the crosshair or reticle when not in GUI mode. @@ -173,12 +194,21 @@ best attack = false # Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). difficulty = 0 +# Replicate how reflected "absorb attribute" spells do not have any effect in Morrowind engine. The caster absorbs the attribute from themselves. +classic reflect absorb attribute behavior = true + # Show duration of magic effect and lights in the spells window. show effect duration = false +# Account for the first follower in fast travel cost calculations. +charge for every follower travelling = false + # Prevents merchants from equipping items that are sold to them. prevent merchant equipping = false +# Make enchanted weaponry without Magical flag bypass normal weapons resistance +enchanted weapons are magical = true + # Makes player followers and escorters start combat with enemies who have started combat with them # or the player. Otherwise they wait for the enemies or the player to do an attack first. followers attack on sight = false @@ -186,6 +216,12 @@ followers attack on sight = false # Can loot non-fighting actors during death animation can loot during death animation = true +# Makes the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch) +rebalance soul gem values = false + +# Allow to load per-group KF-files from Animations folder +use additional anim sources = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). @@ -291,6 +327,10 @@ autosave = true # Display the time played on each save file in the load menu. timeplayed = false +# The maximum number of quick (or auto) save slots to have. +# If all slots are used, the oldest save is reused +max quicksaves = 1 + [Sound] # Name of audio device file. Blank means use the default device. @@ -383,7 +423,7 @@ reflect actors = false # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 -# By what factor water downscales objects. Only works with water shader and refractions on. +# By what factor water downscales objects. Only works with water shader and refractions on. refraction scale = 1.0 [Windows] diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 7b8486fbe..06eebea68 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -47,8 +47,8 @@ vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matS { vec3 lightDir = normalize(gl_LightSource[0].position.xyz); float NdotL = max(dot(viewNormal, lightDir), 0.0); - if (NdotL < 0) - return vec3(0,0,0); + if (NdotL < 0.0) + return vec3(0.,0.,0.); vec3 halfVec = normalize(lightDir - viewDirection); - return pow(max(dot(viewNormal, halfVec), 0.0), 128) * gl_LightSource[0].specular.xyz * matSpec; + return pow(max(dot(viewNormal, halfVec), 0.0), 128.) * gl_LightSource[0].specular.xyz * matSpec; } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 5a72b44b3..ed1322609 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -47,9 +47,9 @@ varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -varying vec4 lighting; +centroid varying vec4 lighting; #else -varying vec4 passColor; +centroid varying vec4 passColor; #endif varying vec3 passViewPos; varying vec3 passNormal; diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index e140cd6ad..4ed02b9c3 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -38,9 +38,9 @@ varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -varying vec4 lighting; +centroid varying vec4 lighting; #else -varying vec4 passColor; +centroid varying vec4 passColor; #endif varying vec3 passViewPos; varying vec3 passNormal; diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 134e838c0..ef6d7f226 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -17,9 +17,9 @@ varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -varying vec4 lighting; +centroid varying vec4 lighting; #else -varying vec4 passColor; +centroid varying vec4 passColor; #endif varying vec3 passViewPos; varying vec3 passNormal; diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index e2a180153..9e7c4a162 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -6,9 +6,9 @@ varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -varying vec4 lighting; +centroid varying vec4 lighting; #else -varying vec4 passColor; +centroid varying vec4 passColor; #endif varying vec3 passViewPos; varying vec3 passNormal; diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 931422d5e..0b220c795 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -2,7 +2,7 @@ #define REFRACTION @refraction_enabled -// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) +// Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- @@ -67,16 +67,16 @@ vec4 circle(vec2 coords, vec2 i_part, float phase) float d = length(toCenter); float r = RAIN_RIPPLE_RADIUS * phase; - + if (d > r) return vec4(0.0,0.0,1.0,0.0); - + float sinValue = (sin(d / r * 1.2) + 0.7) / 2.0; float height = (1.0 - abs(phase)) * pow(sinValue,3.0); vec3 normal = normalize(mix(vec3(0.0,0.0,1.0),vec3(normalize(toCenter),0.0),height)); - + return vec4(normal,height); } @@ -93,7 +93,7 @@ vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and r rain(uv,time) + rain(uv + vec2(10.5,5.7),time) + rain(uv * 0.75 + vec2(3.7,18.9),time) + - rain(uv * 0.9 + vec2(5.7,30.1),time) + + rain(uv * 0.9 + vec2(5.7,30.1),time) + rain(uv * 0.8 + vec2(1.2,3.0),time); } @@ -120,7 +120,7 @@ float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) vec2 normalCoords(vec2 uv, float scale, float speed, float time, float timer1, float timer2, vec3 previousNormal) { return uv * (WAVE_SCALE * scale) + WIND_DIR * time * (WIND_SPEED * speed) -(previousNormal.xy/previousNormal.zz) * WAVE_CHOPPYNESS + vec2(time * timer1,time * timer2); - } + } varying vec3 screenCoordsPassthrough; varying vec4 position; @@ -133,7 +133,7 @@ uniform sampler2D reflectionMap; uniform sampler2D refractionMap; uniform sampler2D refractionDepthMap; #endif - + uniform float osg_SimulationTime; uniform float near; @@ -167,7 +167,7 @@ void main(void) #define waterTimer osg_SimulationTime - vec3 normal0 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.05, 0.04, waterTimer, -0.015, -0.005, vec3(0.0,0.0,0.0))).rgb - 1.0; + vec3 normal0 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.05, 0.04, waterTimer, -0.015, -0.005, vec3(0.0,0.0,0.0))).rgb - 1.0; vec3 normal1 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.1, 0.08, waterTimer, 0.02, 0.015, normal0)).rgb - 1.0; vec3 normal2 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.25, 0.07, waterTimer, -0.04, -0.03, normal1)).rgb - 1.0; vec3 normal3 = 2.0 * texture2D(normalMap,normalCoords(UV, 0.5, 0.09, waterTimer, 0.03, 0.04, normal2)).rgb - 1.0; @@ -180,7 +180,7 @@ void main(void) rainRipple = rainCombined(position.xy / 1000.0,waterTimer) * clamp(rainIntensity,0.0,1.0); else rainRipple = vec4(0.0,0.0,0.0,0.0); - + vec3 rippleAdd = rainRipple.xyz * rainRipple.w * 10.0; vec3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + @@ -231,7 +231,7 @@ void main(void) float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x) * normalization; float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-(normal.xy*REFR_BUMP)).x) * normalization; float surfaceDepth = linearizeDepth(gl_FragCoord.z) * normalization; - float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum + float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum float shore = clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #else float shore = 1.0; diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8a0795d34..7a01ce41d 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -6,18 +6,11 @@ 0 0 - 434 + 671 373 - - - - <html><head/><body><p>This temporary page contains new settings that will be available in-game in a post-1.0 release of OpenMW.</p></body></html> - - - @@ -28,8 +21,8 @@ 0 0 - 393 - 437 + 630 + 746 @@ -109,6 +102,16 @@ + + + + <html><head/><body><p>If this setting is true, the value of filled soul gems is dependent only on soul magnitude.</p><p>The default value is false.</p></body></html> + + + Rebalance soul gem values + + + @@ -139,6 +142,9 @@ + + 1 + Off @@ -207,11 +213,11 @@ - + - Other + Saves - + @@ -222,12 +228,147 @@ + + + + <html><head/><body><p>This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.</p></body></html> + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Maximum Quicksaves + + + + + + + 1 + + + + + + + + + + + + + Testing + + + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + + + + + + Other + + <html><head/><body><p>Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but “jpg”, “png”, and “tga” should be allowed.</p></body></html> - + -1 diff --git a/plugins/mygui_resource_plugin/CMakeLists.txt b/plugins/mygui_resource_plugin/CMakeLists.txt deleted file mode 100644 index be834b17d..000000000 --- a/plugins/mygui_resource_plugin/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -set (MYGUI_RESOURCE_PLUGIN_SOURCES - plugin.hpp - plugin.cpp - plugin_export.cpp -) - -set (MYGUI_RESOURCE_PLUGIN_LIBRARY - Plugin_MyGUI_OpenMW_Resources -) - -add_definitions("-D_USRDLL -DMYGUI_BUILD_DLL") - -add_library(${MYGUI_RESOURCE_PLUGIN_LIBRARY} - SHARED - ${MYGUI_RESOURCE_PLUGIN_SOURCES} - ) - -if(WIN32) - if(MSVC) - # from top-level CMakelists.txt: - # 4305 - Truncating value (double to float, for example) - set_target_properties(${MYGUI_RESOURCE_PLUGIN_LIBRARY} PROPERTIES COMPILE_FLAGS "/wd4305") - endif(MSVC) -endif(WIN32) - -set_target_properties(${MYGUI_RESOURCE_PLUGIN_LIBRARY} PROPERTIES PREFIX "") - -target_link_libraries(${MYGUI_RESOURCE_PLUGIN_LIBRARY} - ${OGRE_LIBRARIES} - components -) diff --git a/plugins/mygui_resource_plugin/plugin.cpp b/plugins/mygui_resource_plugin/plugin.cpp deleted file mode 100644 index 1b718b547..000000000 --- a/plugins/mygui_resource_plugin/plugin.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "plugin.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -//FIXME: code duplication -namespace boost -{ -struct FallbackMap { - std::map mMap; -}; - -void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) -{ - if(v.empty()) - { - v = boost::any(FallbackMap()); - } - - FallbackMap *map = boost::any_cast(&v); - - for(std::vector::const_iterator it=tokens.begin(); it != tokens.end(); ++it) - { - int sep = it->find(","); - if(sep < 1 || sep == (int)it->length()-1) -#if (BOOST_VERSION < 104200) - throw boost::program_options::validation_error("invalid value"); -#else - throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); -#endif - - std::string key(it->substr(0,sep)); - std::string value(it->substr(sep+1)); - - if(map->mMap.find(key) == map->mMap.end()) - { - map->mMap.insert(std::make_pair (key,value)); - } - } -} -} - -namespace MyGUIPlugin -{ - - // Dummy - obsolete when using MyGUI git, because the ScrollBar there has autorepeat support added. - class MWScrollBar : public MyGUI::ScrollBar - { - MYGUI_RTTI_DERIVED(MWScrollBar) - }; - - const std::string& ResourcePlugin::getName() const - { - static const std::string name = "OpenMW resource plugin"; - return name; - } - - void ResourcePlugin::install() - { - - } - void ResourcePlugin::uninstall() - { - - } - - void ResourcePlugin::registerResources() - { - 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::PathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value("")) - ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("fallback-archive", boost::program_options::value >()-> - default_value(std::vector(), "fallback-archive")->multitoken()) - ("encoding", boost::program_options::value()->default_value("win1252")) - ("fallback", boost::program_options::value()->default_value(boost::FallbackMap(), "") - ->multitoken()->composing(), "fallback values"); - - boost::program_options::notify(variables); - - Files::ConfigurationManager cfgManager; - cfgManager.readConfiguration(variables, desc); - - std::vector archives = variables["fallback-archive"].as >(); - bool fsStrict = variables["fs-strict"].as(); - - Files::PathContainer dataDirs, dataLocal; - if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(variables["data"].as()); - } - - std::string local = variables["data-local"].as(); - if (!local.empty()) { - dataLocal.push_back(Files::PathContainer::value_type(local)); - } - - cfgManager.processPaths (dataDirs); - cfgManager.processPaths (dataLocal, true); - - if (!dataLocal.empty()) - dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); - - Files::Collections collections (dataDirs, !fsStrict); - - Bsa::registerResources(collections, archives, true, fsStrict); - - std::string encoding(variables["encoding"].as()); - std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; - - Gui::FontLoader loader(ToUTF8::calculateEncoding(encoding)); - loader.loadAllFonts(false); - - mFallbackMap = variables["fallback"].as().mMap; - } - - void ResourcePlugin::registerWidgets() - { - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - - Gui::registerAllWidgets(); - } - - void ResourcePlugin::createTransparentBGTexture() - { - // This texture is manually created in OpenMW to be able to change its opacity at runtime in the options menu - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual( - "transparent.png", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - 1, 1, - 0, - Ogre::PF_A8R8G8B8, - Ogre::TU_WRITE_ONLY); - std::vector buffer; - buffer.resize(1); - const float val = 0.7; - buffer[0] = (int(255*val) << 24) | (255 << 16) | (255 << 8) | 255; - memcpy(tex->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], 1*4); - tex->getBuffer()->unlock(); - } - - void ResourcePlugin::initialize() - { - MYGUI_LOGGING("OpenMW_Resource_Plugin", Info, "initialize"); - - registerResources(); - registerWidgets(); - createTransparentBGTexture(); - - MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &ResourcePlugin::onRetrieveTag); - } - - void ResourcePlugin::shutdown() - { - /// \todo cleanup - - MYGUI_LOGGING("OpenMW_Resource_Plugin", Info, "shutdown"); - } - - void ResourcePlugin::onRetrieveTag(const MyGUI::UString& tag, MyGUI::UString& out) - { - if (!Gui::replaceTag(tag, out, mFallbackMap)) - out = tag; - } - -} diff --git a/plugins/mygui_resource_plugin/plugin.hpp b/plugins/mygui_resource_plugin/plugin.hpp deleted file mode 100644 index 6a06060d9..000000000 --- a/plugins/mygui_resource_plugin/plugin.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef OPENMW_MYGUI_RESOURCE_PLUGIN_H -#define OPENMW_MYGUI_RESOURCE_PLUGIN_H - -#include -#include - -namespace MyGUIPlugin -{ - - /** - * @brief MyGUI plugin used to register Morrowind resources, custom widgets used in OpenMW, and load Morrowind fonts. - * @paragraph The plugin isn't used in OpenMW itself, but it is useful with the standalone MyGUI tools. To use it, - * change EditorPlugin.xml in Media/Tools/LayoutEditor/EditorPlugin.xml and add an entry for this plugin. - */ - class ResourcePlugin : public MyGUI::IPlugin - { - /*! Get the name of the plugin. - @remarks An implementation must be supplied for this method to uniquely - identify the plugin - */ - virtual const std::string& getName() const; - - /*! Perform the plugin initial installation sequence - */ - virtual void install(); - - /*! Perform any tasks the plugin needs to perform on full system - initialisation. - */ - virtual void initialize(); - - /*! Perform any tasks the plugin needs to perform when the system is shut down - */ - virtual void shutdown(); - - /*! Perform the final plugin uninstallation sequence - */ - virtual void uninstall(); - - private: - void registerResources(); - void registerWidgets(); - void createTransparentBGTexture(); - - void onRetrieveTag(const MyGUI::UString& tag, MyGUI::UString& out); - - std::map mFallbackMap; - }; - -} - -#endif diff --git a/plugins/mygui_resource_plugin/plugin_export.cpp b/plugins/mygui_resource_plugin/plugin_export.cpp deleted file mode 100644 index 0d6b4b804..000000000 --- a/plugins/mygui_resource_plugin/plugin_export.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "plugin.hpp" -#include "MyGUI_PluginManager.h" - -MyGUIPlugin::ResourcePlugin* plugin_item = nullptr; - -extern "C" MYGUI_EXPORT_DLL void dllStartPlugin(void) -{ - plugin_item = new MyGUIPlugin::ResourcePlugin(); - MyGUI::PluginManager::getInstance().installPlugin(plugin_item); -} - -extern "C" MYGUI_EXPORT_DLL void dllStopPlugin(void) -{ - MyGUI::PluginManager::getInstance().uninstallPlugin(plugin_item); - delete plugin_item; - plugin_item = nullptr; -}