From 956234cde999034c5e0a7af27d43818d4252b660 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 2 Jun 2015 23:11:09 +0200 Subject: [PATCH 001/765] Add preliminary appveyor data --- CI/before_script.msvc.sh | 331 +++++++++++++++++++++++++++++++++++++++ CI/build.msvc.sh | 22 +++ appveyor.yml | 20 +++ 3 files changed, 373 insertions(+) create mode 100644 CI/before_script.msvc.sh create mode 100644 CI/build.msvc.sh create mode 100644 appveyor.yml diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh new file mode 100644 index 0000000000..9f94d5f310 --- /dev/null +++ b/CI/before_script.msvc.sh @@ -0,0 +1,331 @@ +#!/bin/bash + +while [ $# -gt 0 ]; do + ARG=$1 + shift + + case $ARG in + -v ) + VERBOSE=true ;; + + x86|i686|win32 ) + platform=i686 ;; + + x64_64|x64|win64 ) + platform=x86_64 ;; + + * ) + echo "Unknown arg $ARG." ;; + esac +done + +if [ -z $VERBOSE ]; then + STRIP="> /dev/null 2>&1" +fi + +cd $(dirname $0)/.. +which appveyor > /dev/null +if [ $? -eq 0 ]; then + VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)" + appveyor UpdateBuild -Version "$VERSION" +fi + +download() { + if ! [ -f $2 ]; then + printf " Downloading $2... " + + if [ -z $VERBOSE ]; then + curl --silent --retry 10 -kLy 5 -o $2 $1 + RET=$? + else + curl --retry 10 -kLy 5 -o $2 $1 + RET=$? + fi + + if [ $RET -ne 0 ]; then + echo "Failed!" + else + echo "Done" + fi + + return $RET + else + echo " $2 exists, skipping." + fi + + return 0 +} + +real_pwd() { + pwd | sed "s,/\(.\),\1:," +} + +msbuild() { + /c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe $@ +} + +CMAKE_OPTS="" +add_cmake_opts() { + CMAKE_OPTS="$CMAKE_OPTS $@" +} + +if [ -z "$ARCH" ]; then + if [ -z "$platform" ]; then + ARCH=`uname -m` + else + ARCH="$platform" + fi +fi + +if [ $ARCH == x86_64 ]; then + ARCHNAME=x86-64 + ARCHSUFFIX=64 + BITS=64 + + BASE_OPTS="-G\"Visual Studio 12 2013 Win64\"" + add_cmake_opts "-G\"Visual Studio 12 2013 Win64\"" +else + ARCHNAME=x86 + ARCHSUFFIX=86 + BITS=32 + + BASE_OPTS="-G\"Visual Studio 12 2013\" -Tv120_xp" + add_cmake_opts "-G\"Visual Studio 12 2013\"" -Tv120_xp +fi + +mkdir -p deps +cd deps + +DEPS="`pwd`" + +echo "Downloading dependency packages." +echo + +# Boost +echo "Boost 1.58.0..." +download http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe boost-1.58.0-win$BITS.exe +echo + +# Bullet +echo "Bullet 2.83.4..." +download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.zip +echo + +# FFmpeg +echo "FFmpeg 2.5.2..." +download http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z ffmpeg$BITS-2.5.2.7z +download http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z ffmpeg$BITS-2.5.2-dev.7z +echo + +# MyGUI +echo "MyGUI 3.2.2..." +download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/MyGUI-3.2.2-win$BITS.7z MyGUI-3.2.2-win$BITS.7z +echo + +# Ogre +echo "Ogre 1.9..." +download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Ogre-1.9-win$BITS.7z Ogre-1.9-win$BITS.7z +echo + +# OpenAL +echo "OpenAL-Soft 1.16.0..." +download http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip OpenAL-Soft-1.16.0.zip +echo + +# Qt +echo "Qt 4.8.6..." +download http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z qt$BITS-4.8.6.7z +echo + +# SDL2 +echo "SDL 2.0.3 binaries..." +download https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip SDL2-2.0.3.zip +echo + +cd .. + +# Set up dependencies +rm -rf build_$BITS +mkdir -p build_$BITS/deps +cd deps + +echo +echo "Extracting dependencies..." +echo + +# Boost +printf "Boost 1.58.0... " +cd ../build_$BITS/deps + +BOOST_SDK="`real_pwd`/Boost" + +$DEPS/boost_1_58_0-msvc-12.0-$BITS.exe //dir="$BOOST_SDK" //verysilent + +add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + +cd $DEPS + +echo Done. +echo + +# Bullet +printf "Bullet 2.83.4... " +cd ../build_$BITS/deps + +eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP +mv Bullet-2.83.4-win$BITS Bullet + +BULLET_SDK="`real_pwd`/Bullet" +add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include" \ + -DBULLET_COLLISION_LIBRARY="$BULLET_SDK/lib/BulletCollision.lib" \ + -DBULLET_COLLISION_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletCollision_Debug.lib" \ + -DBULLET_DYNAMICS_LIBRARY="$BULLET_SDK/lib/BulletDynamics.lib" \ + -DBULLET_DYNAMICS_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletDynamics_Debug.lib" \ + -DBULLET_MATH_LIBRARY="$BULLET_SDK/lib/LinearMath.lib" \ + -DBULLET_MATH_LIBRARY_DEBUG="$BULLET_SDK/lib/LinearMath_Debug.lib" + +cd $DEPS + +echo Done. +echo + +# FFmpeg +printf "FFmpeg 2.5.2... " +cd ../build_$BITS/deps + +eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP +eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP + +mv ffmpeg-2.5.2-win$BITS-shared FFmpeg +cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ +rm -rf ffmpeg-2.5.2-win$BITS-dev + +FFMPEG_SDK="`real_pwd`/FFmpeg" +add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVCODEC_LIBRARIES="$FFMPEG_SDK/lib/avcodec.lib" \ + -DAVDEVICE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVDEVICE_LIBRARIES="$FFMPEG_SDK/lib/avdevice.lib" \ + -DAVFORMAT_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVFORMAT_LIBRARIES="$FFMPEG_SDK/lib/avformat.lib" \ + -DAVUTIL_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVUTIL_LIBRARIES="$FFMPEG_SDK/lib/avutil.lib" \ + -DPOSTPROC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DPOSTPROC_LIBRARIES="$FFMPEG_SDK/lib/postproc.lib" \ + -DSWRESAMPLE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DSWRESAMPLE_LIBRARIES="$FFMPEG_SDK/lib/swresample.lib" \ + -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" + +if [ $BITS -eq 32 ]; then + add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" +fi + +cd $DEPS + +echo Done. +echo + +# Ogre +printf "Ogre 1.9... " +cd ../build_$BITS/deps + +eval 7z x -y $DEPS/Ogre-1.9-win$BITS.7z $STRIP +mv Ogre-1.9-win$BITS Ogre + +OGRE_SDK="`real_pwd`/Ogre" + +add_cmake_opts -DOGRE_SDK="$OGRE_SDK" + +cd $DEPS + +echo Done. +echo + +# MyGUI +printf "MyGUI 3.2.2... " +cd ../build_$BITS/deps + +eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP +mv MyGUI-3.2.2-win$BITS MyGUI + +MYGUI_SDK="`real_pwd`/MyGUI" + +add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ + -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ + -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.hpp" + +cd $DEPS + +echo Done. +echo + +# OpenAL +printf "OpenAL-Soft 1.16.0... " +eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP + +OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" + +add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include" \ + -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + +echo Done. +echo + +# Qt +printf "Qt 4.8.6 binaries... " +cd ../build_$BITS/deps + +eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP +mv qt-4.8.6-* Qt + +QT_SDK="`real_pwd`/Qt" + +cd $QT_SDK +eval qtbinpatcher.exe $STRIP + +add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + +cd $DEPS + +echo Done. +echo + +# SDL2 +printf "SDL 2.0.3 binaries... " +eval 7z x -y SDL2-2.0.3.zip $STRIP + +SDL_SDK="`real_pwd`/SDL2-2.0.3" +add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ + -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ + -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ + -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + +cd $DEPS + +echo Done. +echo + +cd ../build_$BITS + +echo "Building OpenMW." + +add_cmake_opts -DBUILD_BSATOOL=no \ + -DBUILD_ESMTOOL=no \ + -DBUILD_MYGUI_PLUGIN=no + +if [ -z $VERBOSE ]; then + printf " Configuring... " +else + echo " cmake .. $CMAKE_OPTS" +fi + +eval cmake .. $CMAKE_OPTS $STRIP +RET=$? + +if [ -z $VERBOSE ]; then echo Done.; fi + +echo + +exit $RET \ No newline at end of file diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh new file mode 100644 index 0000000000..101aa88275 --- /dev/null +++ b/CI/build.msvc.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +while [ $# -gt 0 ]; do + ARG=$1 + shift + + case $ARG in + x86|i686|win32 ) + BITS=32 ;; + + x64_64|x64|win64 ) + BITS=64 ;; + + * ) + echo "Unknown arg $ARG." + exit 1 ;; + esac +done + +cd $(dirname $0)/../build_$BITS + +msbuild OpenMW.sln //t:Build //p:Configuration=Release //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..3bb773d28c --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,20 @@ +version: "{build}" + +platform: + - Win32 + - x64 + +matrix: + fast_finish: true + +init: + - cmd: cmake --version + - cmd: msbuild /version + +clone_folder: C:\projects\openmw + +build: + - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% + +before_build: + - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh %platform% From db521cddadc51875dcc43ab1108506fc04668ac4 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 2 Jun 2015 23:23:03 +0200 Subject: [PATCH 002/765] Needs to be spaces and not tabs --- appveyor.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3bb773d28c..399be0c389 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,20 +1,20 @@ version: "{build}" platform: - - Win32 - - x64 + - Win32 + - x64 matrix: - fast_finish: true + fast_finish: true init: - - cmd: cmake --version - - cmd: msbuild /version + - cmd: cmake --version + - cmd: msbuild /version clone_folder: C:\projects\openmw build: - - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% + - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% before_build: - - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh %platform% + - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh %platform% From cd4042109a5046707dfa1f07d93e41f9a34d569b Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 2 Jun 2015 23:29:47 +0200 Subject: [PATCH 003/765] Fix the script issues --- CI/build.msvc.sh | 25 ++++++++++--------------- appveyor.yml | 8 ++++++-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index 101aa88275..b19325689c 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -1,22 +1,17 @@ #!/bin/bash -while [ $# -gt 0 ]; do - ARG=$1 - shift +case $1 in + x86|i686|win32 ) + BITS=32 ;; - case $ARG in - x86|i686|win32 ) - BITS=32 ;; + x64_64|x64|win64 ) + BITS=64 ;; - x64_64|x64|win64 ) - BITS=64 ;; - - * ) - echo "Unknown arg $ARG." - exit 1 ;; - esac -done + * ) + echo "Unknown platform $ARG." + exit 1 ;; +esac cd $(dirname $0)/../build_$BITS -msbuild OpenMW.sln //t:Build //p:Configuration=Release //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +msbuild OpenMW.sln //t:Build //p:Configuration=$2 //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/appveyor.yml b/appveyor.yml index 399be0c389..896c0b4646 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,10 @@ platform: - Win32 - x64 +configuration: + - Debug + - Release + matrix: fast_finish: true @@ -13,8 +17,8 @@ init: clone_folder: C:\projects\openmw -build: - - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% +build_script: + - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% %configuration% before_build: - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh %platform% From d7494128a2edd645fa2b4e7bd9a30964a2b3decf Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 2 Jun 2015 23:50:52 +0200 Subject: [PATCH 004/765] More script fixes --- CI/before_script.msvc.sh | 67 ++++++++++++++++++++++++---------------- CI/build.msvc.sh | 10 +++--- appveyor.yml | 20 ++++++++++-- 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 9f94d5f310..8c44a5b9e4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -8,14 +8,9 @@ while [ $# -gt 0 ]; do -v ) VERBOSE=true ;; - x86|i686|win32 ) - platform=i686 ;; - - x64_64|x64|win64 ) - platform=x86_64 ;; - * ) - echo "Unknown arg $ARG." ;; + echo "Unknown arg $ARG." + exit 1 ;; esac done @@ -23,9 +18,14 @@ if [ -z $VERBOSE ]; then STRIP="> /dev/null 2>&1" fi -cd $(dirname $0)/.. -which appveyor > /dev/null -if [ $? -eq 0 ]; then +if [ -z $APPVEYOR ]; then + echo "Running prebuild outside of Appveyor." + + cd $(dirname $0)/.. +else + echo "Running prebuild in Appveyor." + + cd $APPVEYOR_BUILD_FOLDER VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)" appveyor UpdateBuild -Version "$VERSION" fi @@ -70,28 +70,43 @@ add_cmake_opts() { } if [ -z "$ARCH" ]; then - if [ -z "$platform" ]; then + if [ -z "$PLATFORM" ]; then ARCH=`uname -m` else - ARCH="$platform" + ARCH="$PLATFORM" fi fi -if [ $ARCH == x86_64 ]; then - ARCHNAME=x86-64 - ARCHSUFFIX=64 - BITS=64 +case $PLATFORM in + x64|x86_64|x86-64|win64|Win64 ) + ARCHNAME=x86-64 + ARCHSUFFIX=64 + BITS=64 - BASE_OPTS="-G\"Visual Studio 12 2013 Win64\"" - add_cmake_opts "-G\"Visual Studio 12 2013 Win64\"" -else - ARCHNAME=x86 - ARCHSUFFIX=86 - BITS=32 + BASE_OPTS="-G\"Visual Studio 12 2013 Win64\"" + add_cmake_opts "-G\"Visual Studio 12 2013 Win64\"" + ;; - BASE_OPTS="-G\"Visual Studio 12 2013\" -Tv120_xp" - add_cmake_opts "-G\"Visual Studio 12 2013\"" -Tv120_xp -fi + x32|x86|i686|i386|win32|Win32 ) + ARCHNAME=x86 + ARCHSUFFIX=86 + BITS=32 + + BASE_OPTS="-G\"Visual Studio 12 2013\" -Tv120_xp" + add_cmake_opts "-G\"Visual Studio 12 2013\"" -Tv120_xp + ;; + + * ) + echo "Unknown platform $PLATFORM." + exit 1 + ;; +esac + +echo +echo "==========================" +echo "Starting prebuild on win$BITS" +echo "==========================" +echo mkdir -p deps cd deps @@ -108,7 +123,7 @@ echo # Bullet echo "Bullet 2.83.4..." -download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.zip +download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.7z echo # FFmpeg diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index b19325689c..34ce3d0627 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -1,17 +1,17 @@ #!/bin/bash -case $1 in - x86|i686|win32 ) +case $PLATFORM in + x32|x86|i686|i386|win32|Win32 ) BITS=32 ;; - x64_64|x64|win64 ) + x64|x86_64|x86-64|win64|Win64 ) BITS=64 ;; * ) - echo "Unknown platform $ARG." + echo "Unknown platform $PLATFORM." exit 1 ;; esac cd $(dirname $0)/../build_$BITS -msbuild OpenMW.sln //t:Build //p:Configuration=$2 //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +msbuild OpenMW.sln //t:Build //p:Configuration=$CONFIGURATION //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/appveyor.yml b/appveyor.yml index 896c0b4646..f466b2b82d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,14 +11,30 @@ configuration: matrix: fast_finish: true +cache: + - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\Bullet-2.83.4-win64.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\MyGUI-3.2.2-win32.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\MyGUI-3.2.2-win64.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\Ogre-1.9-win32.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\Ogre-1.9-win64.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\ffmpeg32-2.5.2.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\OpenAL-Soft-1.16.0.zip -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\SDL2-2.0.3.zip -> CI/before_script.msvc.sh + init: + - cmd: bash --version - cmd: cmake --version - cmd: msbuild /version + - cmd: echo. clone_folder: C:\projects\openmw build_script: - - cmd: sh C:\projects\openmw\CI\build.msvc.sh %platform% %configuration% + - cmd: sh C:\projects\openmw\CI\build.msvc.sh before_build: - - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh %platform% + - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh From 6993fbaece237f6e0a9d49419e0fd6c31241126a Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 2 Jun 2015 23:52:34 +0200 Subject: [PATCH 005/765] And yet more issues slip past --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 8c44a5b9e4..ca4479deab 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -174,7 +174,7 @@ cd ../build_$BITS/deps BOOST_SDK="`real_pwd`/Boost" -$DEPS/boost_1_58_0-msvc-12.0-$BITS.exe //dir="$BOOST_SDK" //verysilent +$DEPS/boost-1.58.0-win$BITS.exe //dir="$BOOST_SDK" //verysilent add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" From 0a1454d5ecbba634cb2256dee0611e31a988786a Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 00:51:22 +0200 Subject: [PATCH 006/765] Final fixes, moving to Appveyor unstable for boost --- CI/before_script.msvc.sh | 72 +++++++++++++++++++++++++++++++--------- appveyor.yml | 6 ++-- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index ca4479deab..3bd9c490ff 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -21,7 +21,8 @@ fi if [ -z $APPVEYOR ]; then echo "Running prebuild outside of Appveyor." - cd $(dirname $0)/.. + DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + cd $(dirname "$DIR") else echo "Running prebuild in Appveyor." @@ -30,6 +31,33 @@ else appveyor UpdateBuild -Version "$VERSION" fi +run_cmd() { + CMD="$1" + shift + + if [ -z $VERBOSE ]; then + eval $CMD $@ > output.log 2>&1 + RET=$? + + if [ $RET -ne 0 ]; then + if [ -z $APPVEYOR ]; then + echo "Command $CMD failed, output can be found in `real_pwd`/output.log" + exit $RET + else + appveyor PushArtifact output.log -DeploymentName $CMD-output.log + appveyor AddMessage "Command $CMD failed ($RET), output has been pushed as an artifact." -Category Error + fi + else + rm output.log + fi + + return $RET + else + eval $CMD $@ + return $? + fi +} + download() { if ! [ -f $2 ]; then printf " Downloading $2... " @@ -117,9 +145,11 @@ echo "Downloading dependency packages." echo # Boost -echo "Boost 1.58.0..." -download http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe boost-1.58.0-win$BITS.exe -echo +if [ -z $APPVEYOR ]; then + echo "Boost 1.58.0..." + download http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe boost-1.58.0-win$BITS.exe + echo +fi # Bullet echo "Bullet 2.83.4..." @@ -169,20 +199,27 @@ echo "Extracting dependencies..." echo # Boost -printf "Boost 1.58.0... " -cd ../build_$BITS/deps +if [ -z $APPVEYOR ]; then + printf "Boost 1.58.0... " + cd ../build_$BITS/deps -BOOST_SDK="`real_pwd`/Boost" + BOOST_SDK="`real_pwd`/Boost" -$DEPS/boost-1.58.0-win$BITS.exe //dir="$BOOST_SDK" //verysilent + $DEPS/boost-1.58.0-win$BITS.exe //dir="$BOOST_SDK" //verysilent -add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" -cd $DEPS + cd $DEPS -echo Done. -echo + echo Done. + echo +else + # Appveyor unstable has all the boost we need already + BOOST_SDK="c:/Libraries/boost" + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" +fi # Bullet printf "Bullet 2.83.4... " @@ -269,7 +306,7 @@ MYGUI_SDK="`real_pwd`/MyGUI" add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ - -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.hpp" + -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" cd $DEPS @@ -336,10 +373,13 @@ else echo " cmake .. $CMAKE_OPTS" fi -eval cmake .. $CMAKE_OPTS $STRIP +run_cmd cmake .. $CMAKE_OPTS RET=$? -if [ -z $VERBOSE ]; then echo Done.; fi +if [ -z $VERBOSE ]; then + if [ $RET -eq 0 ]; then echo Done.; fi + else echo Failed!; fi +fi echo diff --git a/appveyor.yml b/appveyor.yml index f466b2b82d..890b9684bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,8 @@ configuration: matrix: fast_finish: true +os: unstable + cache: - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh - C:\projects\openmw\deps\Bullet-2.83.4-win64.7z -> CI/before_script.msvc.sh @@ -34,7 +36,7 @@ init: clone_folder: C:\projects\openmw build_script: - - cmd: sh C:\projects\openmw\CI\build.msvc.sh + - cmd: bash --login C:\projects\openmw\CI\build.msvc.sh before_build: - - cmd: sh C:\projects\openmw\CI\before_script.msvc.sh + - cmd: bash --login C:\projects\openmw\CI\before_script.msvc.sh From ba9b21bc296f25d5dd1d951badd44544fd195ee7 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 01:18:10 +0200 Subject: [PATCH 007/765] Moving dependencies to a better place --- CI/before_script.msvc.sh | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 3bd9c490ff..56d56762f6 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -44,8 +44,10 @@ run_cmd() { echo "Command $CMD failed, output can be found in `real_pwd`/output.log" exit $RET else - appveyor PushArtifact output.log -DeploymentName $CMD-output.log - appveyor AddMessage "Command $CMD failed ($RET), output has been pushed as an artifact." -Category Error + 7z a output.7z output.log > /dev/null 2>&1 + + appveyor PushArtifact output.7z -DeploymentName $CMD-output.7z + appveyor AddMessage "Command $CMD failed (code $RET), output has been pushed as an artifact." -Category Error fi else rm output.log @@ -153,7 +155,7 @@ fi # Bullet echo "Bullet 2.83.4..." -download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.7z +download http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.7z echo # FFmpeg @@ -164,12 +166,12 @@ echo # MyGUI echo "MyGUI 3.2.2..." -download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/MyGUI-3.2.2-win$BITS.7z MyGUI-3.2.2-win$BITS.7z +download http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z MyGUI-3.2.2-win$BITS.7z echo # Ogre echo "Ogre 1.9..." -download https://gist.github.com/ace13/dc6aad628d48338d590e/raw/Ogre-1.9-win$BITS.7z Ogre-1.9-win$BITS.7z +download http://www.lysator.liu.se/~ace/OpenMW/deps/Ogre-1.9-win$BITS.7z Ogre-1.9-win$BITS.7z echo # OpenAL @@ -196,7 +198,6 @@ cd deps echo echo "Extracting dependencies..." -echo # Boost if [ -z $APPVEYOR ]; then @@ -213,7 +214,6 @@ if [ -z $APPVEYOR ]; then cd $DEPS echo Done. - echo else # Appveyor unstable has all the boost we need already BOOST_SDK="c:/Libraries/boost" @@ -240,7 +240,6 @@ add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include" \ cd $DEPS echo Done. -echo # FFmpeg printf "FFmpeg 2.5.2... " @@ -276,7 +275,6 @@ fi cd $DEPS echo Done. -echo # Ogre printf "Ogre 1.9... " @@ -292,7 +290,6 @@ add_cmake_opts -DOGRE_SDK="$OGRE_SDK" cd $DEPS echo Done. -echo # MyGUI printf "MyGUI 3.2.2... " @@ -311,7 +308,6 @@ add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ cd $DEPS echo Done. -echo # OpenAL printf "OpenAL-Soft 1.16.0... " @@ -323,10 +319,9 @@ add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include" \ -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" echo Done. -echo # Qt -printf "Qt 4.8.6 binaries... " +printf "Qt 4.8.6... " cd ../build_$BITS/deps eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP @@ -342,10 +337,9 @@ add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" cd $DEPS echo Done. -echo # SDL2 -printf "SDL 2.0.3 binaries... " +printf "SDL 2.0.3... " eval 7z x -y SDL2-2.0.3.zip $STRIP SDL_SDK="`real_pwd`/SDL2-2.0.3" @@ -361,7 +355,7 @@ echo cd ../build_$BITS -echo "Building OpenMW." +echo "Building OpenMW..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ @@ -377,8 +371,8 @@ run_cmd cmake .. $CMAKE_OPTS RET=$? if [ -z $VERBOSE ]; then - if [ $RET -eq 0 ]; then echo Done.; fi - else echo Failed!; fi + if [ $RET -eq 0 ]; then echo Done. + else echo Failed.; fi fi echo From 20b7c7b46cf3ada8aa888c51f250cf991bb6a090 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 01:41:45 +0200 Subject: [PATCH 008/765] Hopefully the last fixes, build should run --- CI/before_script.msvc.sh | 16 ++++++++-------- CI/build.msvc.sh | 21 ++++++++++++++++++++- appveyor.yml | 2 ++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 56d56762f6..cd4190ea97 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -22,7 +22,7 @@ if [ -z $APPVEYOR ]; then echo "Running prebuild outside of Appveyor." DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") - cd $(dirname "$DIR") + cd $(dirname "$DIR")/.. else echo "Running prebuild in Appveyor." @@ -99,12 +99,12 @@ add_cmake_opts() { CMAKE_OPTS="$CMAKE_OPTS $@" } -if [ -z "$ARCH" ]; then - if [ -z "$PLATFORM" ]; then - ARCH=`uname -m` - else - ARCH="$PLATFORM" - fi +if [ -z $PLATFORM ]; then + PLATFORM=`uname -m` +fi + +if [ -z $CONFIGURATION ]; then + CONFIGURATION="Debug" fi case $PLATFORM in @@ -206,7 +206,7 @@ if [ -z $APPVEYOR ]; then BOOST_SDK="`real_pwd`/Boost" - $DEPS/boost-1.58.0-win$BITS.exe //dir="$BOOST_SDK" //verysilent + $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index 34ce3d0627..57f4f6c1d6 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -1,5 +1,13 @@ #!/bin/bash +if [ -z $PLATFORM ]; then + PLATFORM=`uname -m` +fi + +if [ -z $CONFIGURATION ]; then + CONFIGURATION="Debug" +fi + case $PLATFORM in x32|x86|i686|i386|win32|Win32 ) BITS=32 ;; @@ -12,6 +20,17 @@ case $PLATFORM in exit 1 ;; esac -cd $(dirname $0)/../build_$BITS +if [ -z $APPVEYOR ]; then + echo "Running $BITS-bit $CONFIGURATION build outside of Appveyor." + + DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + cd $(dirname "$DIR")/.. +else + echo "Running $BITS-bit $CONFIGURATION build in Appveyor." + + cd $APPVEYOR_BUILD_FOLDER +fi + +cd build_$BITS msbuild OpenMW.sln //t:Build //p:Configuration=$CONFIGURATION //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/appveyor.yml b/appveyor.yml index 890b9684bd..3f8b27e22f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,6 +13,8 @@ matrix: os: unstable +shallow_clone: true + cache: - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh - C:\projects\openmw\deps\Bullet-2.83.4-win64.7z -> CI/before_script.msvc.sh From 108c2719a85ca915bfe90a67a854fe18cad83fad Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 02:04:31 +0200 Subject: [PATCH 009/765] And yet I forgot about msbuild, should work now --- CI/before_script.msvc.sh | 6 +----- CI/build.msvc.sh | 7 +++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index cd4190ea97..cefc06fe9e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -46,7 +46,7 @@ run_cmd() { else 7z a output.7z output.log > /dev/null 2>&1 - appveyor PushArtifact output.7z -DeploymentName $CMD-output.7z + appveyor PushArtifact output.7z -FileName $CMD-output.7z appveyor AddMessage "Command $CMD failed (code $RET), output has been pushed as an artifact." -Category Error fi else @@ -90,10 +90,6 @@ real_pwd() { pwd | sed "s,/\(.\),\1:," } -msbuild() { - /c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe $@ -} - CMAKE_OPTS="" add_cmake_opts() { CMAKE_OPTS="$CMAKE_OPTS $@" diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index 57f4f6c1d6..b17e7b94c6 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -33,4 +33,11 @@ fi cd build_$BITS +which msbuild > /dev/null +if [ $? -ne 0 ]; then + msbuild() { + /c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe "$@" + } +fi + msbuild OpenMW.sln //t:Build //p:Configuration=$CONFIGURATION //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" From 3425e9b1d14806cae73d7bc6256c65af55b0206b Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 02:34:45 +0200 Subject: [PATCH 010/765] And done. Switched to a slight less shallow clone too, for better versioning --- CI/before_script.msvc.sh | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index cefc06fe9e..36f8213d45 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -225,7 +225,7 @@ eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP mv Bullet-2.83.4-win$BITS Bullet BULLET_SDK="`real_pwd`/Bullet" -add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include" \ +add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ -DBULLET_COLLISION_LIBRARY="$BULLET_SDK/lib/BulletCollision.lib" \ -DBULLET_COLLISION_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletCollision_Debug.lib" \ -DBULLET_DYNAMICS_LIBRARY="$BULLET_SDK/lib/BulletDynamics.lib" \ diff --git a/appveyor.yml b/appveyor.yml index 3f8b27e22f..e46dd3dd8c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ matrix: os: unstable -shallow_clone: true +clone_depth: 5 cache: - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh From e49cf2888de2c8a0ef5e3189afbeda0d674d04c9 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 3 Jun 2015 10:15:28 +0200 Subject: [PATCH 011/765] Let's disable everything except OpenMW itself Just to check if it's at all possible to use AppVeyor --- CI/before_script.msvc.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 36f8213d45..66f74520c4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -355,7 +355,12 @@ echo "Building OpenMW..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ - -DBUILD_MYGUI_PLUGIN=no + -DBUILD_MYGUI_PLUGIN=no \ + -DBUILD_OPENCS=no \ + -DBUILD_WIZARD=no \ + -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no if [ -z $VERBOSE ]; then printf " Configuring... " @@ -373,4 +378,4 @@ fi echo -exit $RET \ No newline at end of file +exit $RET From 4f2cd1711944b12d6a90d9a54332d91d0ec96c40 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 8 Jun 2015 02:14:20 +0200 Subject: [PATCH 012/765] Let's get some OSG builds going --- CI/before_script.msvc.sh | 260 +++++++++++++++++++++++---------------- CMakeLists.txt | 3 + 2 files changed, 158 insertions(+), 105 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 36f8213d45..a12c170384 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -8,6 +8,15 @@ while [ $# -gt 0 ]; do -v ) VERBOSE=true ;; + -d ) + SKIP_DOWNLOAD=true ;; + + -e ) + SKIP_EXTRACT=true ;; + + -k ) + KEEP=true ;; + * ) echo "Unknown arg $ARG." exit 1 ;; @@ -61,29 +70,46 @@ run_cmd() { } download() { - if ! [ -f $2 ]; then - printf " Downloading $2... " - - if [ -z $VERBOSE ]; then - curl --silent --retry 10 -kLy 5 -o $2 $1 - RET=$? - else - curl --retry 10 -kLy 5 -o $2 $1 - RET=$? - fi - - if [ $RET -ne 0 ]; then - echo "Failed!" - else - echo "Done" - fi - - return $RET - else - echo " $2 exists, skipping." + if [ $# -lt 3 ]; then + echo "Invalid parameters to download." + return 1 fi - return 0 + NAME=$1 + shift + + echo "$NAME..." + + while [ $# -gt 1 ]; do + URL=$1 + FILE=$2 + shift + shift + + if ! [ -f $FILE ]; then + printf " Downloading $FILE... " + + if [ -z $VERBOSE ]; then + curl --silent --retry 10 -kLy 5 -o $FILE $URL + RET=$? + else + curl --retry 10 -kLy 5 -o $FILE $URL + RET=$? + fi + + if [ $RET -ne 0 ]; then + echo "Failed!" + else + echo "Done." + fi + else + echo " $FILE exists, skipping." + fi + done + + if [ $# -ne 0 ]; then + echo "Missing parameter." + fi } real_pwd() { @@ -134,63 +160,71 @@ echo "Starting prebuild on win$BITS" echo "==========================" echo +# cd OpenMW/AppVeyor-test mkdir -p deps cd deps DEPS="`pwd`" -echo "Downloading dependency packages." -echo - -# Boost -if [ -z $APPVEYOR ]; then - echo "Boost 1.58.0..." - download http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe boost-1.58.0-win$BITS.exe +if [ -z $SKIP_DOWNLOAD ]; then + echo "Downloading dependency packages." echo + + # Boost + if [ -z $APPVEYOR ]; then + download "Boost 1.58.0" \ + http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe \ + boost-1.58.0-win$BITS.exe + fi + + # Bullet + download "Bullet 2.83.4" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.4-win$BITS.7z \ + Bullet-2.83.4-win$BITS.7z + + # FFmpeg + download "FFmpeg 2.5.2" \ + http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z \ + ffmpeg$BITS-2.5.2.7z \ + http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z \ + ffmpeg$BITS-2.5.2-dev.7z + + # MyGUI + download "MyGUI 3.2.2" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z \ + MyGUI-3.2.2-win$BITS.7z + + # OpenAL + download "OpenAL-Soft 1.16.0" \ + http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip \ + OpenAL-Soft-1.16.0.zip + + # OSG + download "OpenSceneGraph 3.3.8" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.3.8-win$BITS.7z \ + OSG-3.3.8-win$BITS.7z + + # Qt + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + + # SDL2 + download "SDL 2.0.3" \ + https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip \ + SDL2-2.0.3.zip fi -# Bullet -echo "Bullet 2.83.4..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.7z -echo - -# FFmpeg -echo "FFmpeg 2.5.2..." -download http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z ffmpeg$BITS-2.5.2.7z -download http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z ffmpeg$BITS-2.5.2-dev.7z -echo - -# MyGUI -echo "MyGUI 3.2.2..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z MyGUI-3.2.2-win$BITS.7z -echo - -# Ogre -echo "Ogre 1.9..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/Ogre-1.9-win$BITS.7z Ogre-1.9-win$BITS.7z -echo - -# OpenAL -echo "OpenAL-Soft 1.16.0..." -download http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip OpenAL-Soft-1.16.0.zip -echo - -# Qt -echo "Qt 4.8.6..." -download http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z qt$BITS-4.8.6.7z -echo - -# SDL2 -echo "SDL 2.0.3 binaries..." -download https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip SDL2-2.0.3.zip -echo - -cd .. +cd .. #/.. # Set up dependencies -rm -rf build_$BITS -mkdir -p build_$BITS/deps -cd deps +if [ -z $KEEP ]; then + rm -rf OSG_$BITS + mkdir -p OSG_$BITS/deps +fi +cd OSG_$BITS/deps + +DEPS_INSTALL=`pwd` echo echo "Extracting dependencies..." @@ -198,11 +232,13 @@ echo "Extracting dependencies..." # Boost if [ -z $APPVEYOR ]; then printf "Boost 1.58.0... " - cd ../build_$BITS/deps + cd $DEPS_INSTALL BOOST_SDK="`real_pwd`/Boost" - $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + if [ -z $SKIP_EXTRACT ]; then + $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" @@ -219,10 +255,12 @@ fi # Bullet printf "Bullet 2.83.4... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP -mv Bullet-2.83.4-win$BITS Bullet +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP + mv Bullet-2.83.4-win$BITS Bullet +fi BULLET_SDK="`real_pwd`/Bullet" add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ @@ -239,14 +277,16 @@ echo Done. # FFmpeg printf "FFmpeg 2.5.2... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP -eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP -mv ffmpeg-2.5.2-win$BITS-shared FFmpeg -cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ -rm -rf ffmpeg-2.5.2-win$BITS-dev + mv ffmpeg-2.5.2-win$BITS-shared FFmpeg + cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ + rm -rf ffmpeg-2.5.2-win$BITS-dev +fi FFMPEG_SDK="`real_pwd`/FFmpeg" add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ @@ -272,33 +312,19 @@ cd $DEPS echo Done. -# Ogre -printf "Ogre 1.9... " -cd ../build_$BITS/deps - -eval 7z x -y $DEPS/Ogre-1.9-win$BITS.7z $STRIP -mv Ogre-1.9-win$BITS Ogre - -OGRE_SDK="`real_pwd`/Ogre" - -add_cmake_opts -DOGRE_SDK="$OGRE_SDK" - -cd $DEPS - -echo Done. - # MyGUI printf "MyGUI 3.2.2... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP -mv MyGUI-3.2.2-win$BITS MyGUI +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP + mv MyGUI-3.2.2-win$BITS MyGUI +fi MYGUI_SDK="`real_pwd`/MyGUI" add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" cd $DEPS @@ -307,7 +333,9 @@ echo Done. # OpenAL printf "OpenAL-Soft 1.16.0... " -eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP +fi OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" @@ -316,12 +344,31 @@ add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include" \ echo Done. +# OSG +printf "OSG 3.3.8... " +cd $DEPS_INSTALL + +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP + mv OSG-3.3.8-win$BITS OSG +fi + +OSG_SDK="`real_pwd`/OSG" + +add_cmake_opts -DOSG_DIR="$OSG_SDK" + +cd $DEPS + +echo Done. + # Qt printf "Qt 4.8.6... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP -mv qt-4.8.6-* Qt +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP + mv qt-4.8.6-* Qt +fi QT_SDK="`real_pwd`/Qt" @@ -336,7 +383,10 @@ echo Done. # SDL2 printf "SDL 2.0.3... " -eval 7z x -y SDL2-2.0.3.zip $STRIP + +if [ -z $SKIP_EXTRACT ]; then + eval 7z x -y SDL2-2.0.3.zip $STRIP +fi SDL_SDK="`real_pwd`/SDL2-2.0.3" add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ @@ -349,7 +399,7 @@ cd $DEPS echo Done. echo -cd ../build_$BITS +cd $DEPS_INSTALL/.. echo "Building OpenMW..." diff --git a/CMakeLists.txt b/CMakeLists.txt index 77b054ff51..f988384528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,9 @@ if (WIN32) # Suppress WinMain(), provided by SDL add_definitions(-DSDL_MAIN_HANDLED) + + # Get rid of useless crud from windows.h + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() # Dependencies From c71bbd02bfccb1507d984e36127eaf54acd3205e Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 8 Jun 2015 02:26:23 +0200 Subject: [PATCH 013/765] Finish up the OSG build scripts and configuration Sadly it seems like Appveyor can't build OpenMW without timing out yet. More build-time improvements are in order --- CI/before_script.msvc.sh | 186 ++++++++++++++++++++++++++++++++++----- CI/build.msvc.sh | 18 +++- CMakeLists.txt | 3 - appveyor.yml | 31 +++---- 4 files changed, 197 insertions(+), 41 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index a12c170384..367a402a09 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -121,6 +121,11 @@ add_cmake_opts() { CMAKE_OPTS="$CMAKE_OPTS $@" } +RUNTIME_DLLS="" +add_runtime_dlls() { + RUNTIME_DLLS="$RUNTIME_DLLS $@" +} + if [ -z $PLATFORM ]; then PLATFORM=`uname -m` fi @@ -154,6 +159,20 @@ case $PLATFORM in ;; esac +case $CONFIGURATION in + debug|Debug|DEBUG ) + CONFIGURATION=Debug + ;; + + release|Release|RELEASE ) + CONFIGURATION=Release + ;; + + relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) + CONFIGURATION=RelWithDebInfo + ;; +esac + echo echo "==========================" echo "Starting prebuild on win$BITS" @@ -178,9 +197,9 @@ if [ -z $SKIP_DOWNLOAD ]; then fi # Bullet - download "Bullet 2.83.4" \ - http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.4-win$BITS.7z \ - Bullet-2.83.4-win$BITS.7z + download "Bullet 2.83.5" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.5-win$BITS.7z \ + Bullet-2.83.5-win$BITS.7z # FFmpeg download "FFmpeg 2.5.2" \ @@ -219,10 +238,16 @@ cd .. #/.. # Set up dependencies if [ -z $KEEP ]; then - rm -rf OSG_$BITS - mkdir -p OSG_$BITS/deps + echo + printf "Preparing build directory... " + + rm -rf Build_$BITS + mkdir -p Build_$BITS/deps + + echo Done. fi -cd OSG_$BITS/deps +mkdir -p Build_$BITS/deps +cd Build_$BITS/deps DEPS_INSTALL=`pwd` @@ -236,7 +261,10 @@ if [ -z $APPVEYOR ]; then BOOST_SDK="`real_pwd`/Boost" - if [ -z $SKIP_EXTRACT ]; then + if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Boost $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent fi @@ -254,12 +282,15 @@ else fi # Bullet -printf "Bullet 2.83.4... " +printf "Bullet 2.83.5... " cd $DEPS_INSTALL -if [ -z $SKIP_EXTRACT ]; then - eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP - mv Bullet-2.83.4-win$BITS Bullet +if [ -d Bullet ]; then + printf "Exists. (No version checking) " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf Bullet + eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP + mv Bullet-2.83.5-win$BITS Bullet fi BULLET_SDK="`real_pwd`/Bullet" @@ -279,7 +310,11 @@ echo Done. printf "FFmpeg 2.5.2... " cd $DEPS_INSTALL -if [ -z $SKIP_EXTRACT ]; then +if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf FFmpeg + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP @@ -304,6 +339,8 @@ add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" +add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll + if [ $BITS -eq 32 ]; then add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" fi @@ -316,7 +353,14 @@ echo Done. printf "MyGUI 3.2.2... " cd $DEPS_INSTALL -if [ -z $SKIP_EXTRACT ]; then +if [ -d MyGUI ] && \ + grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null +then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf MyGUI eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP mv MyGUI-3.2.2-win$BITS MyGUI fi @@ -327,28 +371,47 @@ add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="_d" +else + SUFFIX="" +fi +add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll + cd $DEPS echo Done. # OpenAL printf "OpenAL-Soft 1.16.0... " -if [ -z $SKIP_EXTRACT ]; then +if [ -d openal-soft-1.16.0-bin ]; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf openal-soft-1.16.0-bin eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP fi OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" -add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include" \ +add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + + echo Done. # OSG printf "OSG 3.3.8... " cd $DEPS_INSTALL -if [ -z $SKIP_EXTRACT ]; then +if [ -d OSG ] && \ + grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null +then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf OSG eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP mv OSG-3.3.8-win$BITS OSG fi @@ -357,6 +420,21 @@ OSG_SDK="`real_pwd`/OSG" add_cmake_opts -DOSG_DIR="$OSG_SDK" +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d" +else + SUFFIX="" +fi +add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \ + `pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll + +OSG_PLUGINS="" +add_osg_dlls() { + OSG_PLUGINS="$OSG_PLUGINS $@" +} + +add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll + cd $DEPS echo Done. @@ -365,7 +443,10 @@ echo Done. printf "Qt 4.8.6... " cd $DEPS_INSTALL -if [ -z $SKIP_EXTRACT ]; then +if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf Qt eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP mv qt-4.8.6-* Qt fi @@ -377,6 +458,13 @@ eval qtbinpatcher.exe $STRIP add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d4" +else + SUFFIX="4" +fi +add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + cd $DEPS echo Done. @@ -384,7 +472,10 @@ echo Done. # SDL2 printf "SDL 2.0.3... " -if [ -z $SKIP_EXTRACT ]; then +if [ -d SDL2-2.0.3 ]; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf SDL2-2.0.3 eval 7z x -y SDL2-2.0.3.zip $STRIP fi @@ -394,6 +485,8 @@ add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" +add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll + cd $DEPS echo Done. @@ -401,11 +494,50 @@ echo cd $DEPS_INSTALL/.. -echo "Building OpenMW..." +echo "Setting up OpenMW build..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ - -DBUILD_MYGUI_PLUGIN=no + -DBUILD_MYGUI_PLUGIN=no \ + -DOPENMW_MP_BUILD=yes + +if [ -z $APPVEYOR ]; then + echo " (Outside of AppVeyor, doing full build.)" +else + case $STEP in + components ) + echo " Subproject: Components." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + rm -rf components + ;; + openmw ) + echo " Subproject: OpenMW." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_WIZARD=no + ;; + opencs ) + echo " Subproject: OpenCS." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + ;; + misc ) + echo " Subproject: Misc." + add_cmake_opts -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no + ;; + esac +fi if [ -z $VERBOSE ]; then printf " Configuring... " @@ -423,4 +555,18 @@ fi echo +echo "Copying Runtime DLLs..." +mkdir -p $CONFIGURATION +for DLL in $RUNTIME_DLLS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/ +done +echo "OSG Plugin DLLs..." +mkdir -p $CONFIGURATION/osgPlugins-3.3.8 +for DLL in $OSG_PLUGINS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/osgPlugins-3.3.8 +done +echo + exit $RET \ No newline at end of file diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index b17e7b94c6..d426ef90d4 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -10,10 +10,14 @@ fi case $PLATFORM in x32|x86|i686|i386|win32|Win32 ) - BITS=32 ;; + BITS=32 + PLATFORM=Win32 + ;; x64|x86_64|x86-64|win64|Win64 ) - BITS=64 ;; + BITS=64 + PLATFORM=x64 + ;; * ) echo "Unknown platform $PLATFORM." @@ -40,4 +44,12 @@ if [ $? -ne 0 ]; then } fi -msbuild OpenMW.sln //t:Build //p:Configuration=$CONFIGURATION //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +if [ -z $APPVEYOR ]; then + msbuild OpenMW.sln //t:Build //m:8 +else + msbuild OpenMW.sln //t:Build //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +fi + +if [ ! -z $PACKAGE ]; then + msbuild PACKAGE.vcxproj //t:Build //m:8 +fi \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f988384528..77b054ff51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,9 +138,6 @@ if (WIN32) # Suppress WinMain(), provided by SDL add_definitions(-DSDL_MAIN_HANDLED) - - # Get rid of useless crud from windows.h - add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() # Dependencies diff --git a/appveyor.yml b/appveyor.yml index e46dd3dd8c..a229a2212c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,28 +6,27 @@ platform: configuration: - Debug - - Release + +environment: + matrix: + - STEP: openmw + - STEP: opencs + - STEP: misc matrix: fast_finish: true os: unstable -clone_depth: 5 +clone_depth: 1 cache: - - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Bullet-2.83.4-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\MyGUI-3.2.2-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\MyGUI-3.2.2-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Ogre-1.9-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Ogre-1.9-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg32-2.5.2.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\OpenAL-Soft-1.16.0.zip -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\SDL2-2.0.3.zip -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\Bullet-2.83.5-win32.7z + - C:\projects\openmw\deps\Bullet-2.83.5-win64.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-win32.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-win64.7z + - C:\projects\openmw\deps\OSG-3.3.8-win32.7z + - C:\projects\openmw\deps\OSG-3.3.8-win64.7z init: - cmd: bash --version @@ -41,4 +40,6 @@ build_script: - cmd: bash --login C:\projects\openmw\CI\build.msvc.sh before_build: - - cmd: bash --login C:\projects\openmw\CI\before_script.msvc.sh + - cmd: bash --login C:\projects\openmw\CI\before_script.msvc.sh -k + +test: off From a1e29162bdb6da9069394c439a90a7e96cfa4e01 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 24 Jun 2015 15:54:00 +0200 Subject: [PATCH 014/765] Bring Ogre pre-build script up to OSG standard --- CI/before_script.msvc.sh | 393 ++++++++++++++++++++++++++++----------- 1 file changed, 284 insertions(+), 109 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 66f74520c4..ae74146bc2 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -8,6 +8,15 @@ while [ $# -gt 0 ]; do -v ) VERBOSE=true ;; + -d ) + SKIP_DOWNLOAD=true ;; + + -e ) + SKIP_EXTRACT=true ;; + + -k ) + KEEP=true ;; + * ) echo "Unknown arg $ARG." exit 1 ;; @@ -61,29 +70,46 @@ run_cmd() { } download() { - if ! [ -f $2 ]; then - printf " Downloading $2... " - - if [ -z $VERBOSE ]; then - curl --silent --retry 10 -kLy 5 -o $2 $1 - RET=$? - else - curl --retry 10 -kLy 5 -o $2 $1 - RET=$? - fi - - if [ $RET -ne 0 ]; then - echo "Failed!" - else - echo "Done" - fi - - return $RET - else - echo " $2 exists, skipping." + if [ $# -lt 3 ]; then + echo "Invalid parameters to download." + return 1 fi - return 0 + NAME=$1 + shift + + echo "$NAME..." + + while [ $# -gt 1 ]; do + URL=$1 + FILE=$2 + shift + shift + + if ! [ -f $FILE ]; then + printf " Downloading $FILE... " + + if [ -z $VERBOSE ]; then + curl --silent --retry 10 -kLy 5 -o $FILE $URL + RET=$? + else + curl --retry 10 -kLy 5 -o $FILE $URL + RET=$? + fi + + if [ $RET -ne 0 ]; then + echo "Failed!" + else + echo "Done." + fi + else + echo " $FILE exists, skipping." + fi + done + + if [ $# -ne 0 ]; then + echo "Missing parameter." + fi } real_pwd() { @@ -95,6 +121,11 @@ add_cmake_opts() { CMAKE_OPTS="$CMAKE_OPTS $@" } +RUNTIME_DLLS="" +add_runtime_dlls() { + RUNTIME_DLLS="$RUNTIME_DLLS $@" +} + if [ -z $PLATFORM ]; then PLATFORM=`uname -m` fi @@ -128,6 +159,21 @@ case $PLATFORM in ;; esac + +case $CONFIGURATION in + debug|Debug|DEBUG ) + CONFIGURATION=Debug + ;; + + release|Release|RELEASE ) + CONFIGURATION=Release + ;; + + relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) + CONFIGURATION=RelWithDebInfo + ;; +esac + echo echo "==========================" echo "Starting prebuild on win$BITS" @@ -139,58 +185,71 @@ cd deps DEPS="`pwd`" -echo "Downloading dependency packages." -echo - -# Boost -if [ -z $APPVEYOR ]; then - echo "Boost 1.58.0..." - download http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe boost-1.58.0-win$BITS.exe +if [ -z $SKIP_DOWNLOAD ]; then + echo "Downloading dependency packages." echo + + # Boost + if [ -z $APPVEYOR ]; then + download "Boost 1.58.0" \ + http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe \ + boost-1.58.0-win$BITS.exe + fi + + # Bullet + download "Bullet 2.83.5" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.5-win$BITS.7z \ + Bullet-2.83.5-win$BITS.7z + + # FFmpeg + download "FFmpeg 2.5.2" \ + http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z \ + ffmpeg$BITS-2.5.2.7z \ + http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z \ + ffmpeg$BITS-2.5.2-dev.7z + + # MyGUI + download "MyGUI 3.2.2" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z \ + MyGUI-3.2.2-win$BITS.7z + + # OpenAL + download "OpenAL-Soft 1.16.0" \ + http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip \ + OpenAL-Soft-1.16.0.zip + + # Ogre + download "Ogre 1.9" \ + http://www.lysator.liu.se/~ace/OpenMW/deps/Ogre-1.9-win$BITS.7z \ + Ogre-1.9-win$BITS.7z + + # Qt + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + + # SDL2 + download "SDL 2.0.3" \ + https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip \ + SDL2-2.0.3.zip fi -# Bullet -echo "Bullet 2.83.4..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.4-win$BITS.7z Bullet-2.83.4-win$BITS.7z -echo - -# FFmpeg -echo "FFmpeg 2.5.2..." -download http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z ffmpeg$BITS-2.5.2.7z -download http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z ffmpeg$BITS-2.5.2-dev.7z -echo - -# MyGUI -echo "MyGUI 3.2.2..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z MyGUI-3.2.2-win$BITS.7z -echo - -# Ogre -echo "Ogre 1.9..." -download http://www.lysator.liu.se/~ace/OpenMW/deps/Ogre-1.9-win$BITS.7z Ogre-1.9-win$BITS.7z -echo - -# OpenAL -echo "OpenAL-Soft 1.16.0..." -download http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip OpenAL-Soft-1.16.0.zip -echo - -# Qt -echo "Qt 4.8.6..." -download http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z qt$BITS-4.8.6.7z -echo - -# SDL2 -echo "SDL 2.0.3 binaries..." -download https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip SDL2-2.0.3.zip -echo - cd .. # Set up dependencies -rm -rf build_$BITS -mkdir -p build_$BITS/deps -cd deps +if [ -z $KEEP ]; then + echo + printf "Preparing build directory... " + + rm -rf Build_$BITS + mkdir -p Build_$BITS/deps + + echo Done. +fi +mkdir -p Build_$BITS/deps +cd Build_$BITS/deps + +DEPS_INSTALL=`pwd` echo echo "Extracting dependencies..." @@ -198,11 +257,16 @@ echo "Extracting dependencies..." # Boost if [ -z $APPVEYOR ]; then printf "Boost 1.58.0... " - cd ../build_$BITS/deps + cd $DEPS_INSTALL BOOST_SDK="`real_pwd`/Boost" - $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Boost + $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" @@ -218,11 +282,16 @@ else fi # Bullet -printf "Bullet 2.83.4... " -cd ../build_$BITS/deps +printf "Bullet 2.83.5... " +cd $DEPS_INSTALL -eval 7z x -y $DEPS/Bullet-2.83.4-win$BITS.7z $STRIP -mv Bullet-2.83.4-win$BITS Bullet +if [ -d Bullet ]; then + printf "Exists. (No version checking) " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf Bullet + eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP + mv Bullet-2.83.5-win$BITS Bullet +fi BULLET_SDK="`real_pwd`/Bullet" add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ @@ -239,14 +308,20 @@ echo Done. # FFmpeg printf "FFmpeg 2.5.2... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP -eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP +if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf FFmpeg -mv ffmpeg-2.5.2-win$BITS-shared FFmpeg -cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ -rm -rf ffmpeg-2.5.2-win$BITS-dev + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP + + mv ffmpeg-2.5.2-win$BITS-shared FFmpeg + cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ + rm -rf ffmpeg-2.5.2-win$BITS-dev +fi FFMPEG_SDK="`real_pwd`/FFmpeg" add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ @@ -264,6 +339,8 @@ add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" +add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll + if [ $BITS -eq 32 ]; then add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" fi @@ -272,34 +349,64 @@ cd $DEPS echo Done. -# Ogre -printf "Ogre 1.9... " -cd ../build_$BITS/deps +# MyGUI +printf "MyGUI 3.2.2... " +cd $DEPS_INSTALL -eval 7z x -y $DEPS/Ogre-1.9-win$BITS.7z $STRIP -mv Ogre-1.9-win$BITS Ogre +if [ -d MyGUI ] && \ + grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null +then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf MyGUI + eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP + mv MyGUI-3.2.2-win$BITS MyGUI +fi -OGRE_SDK="`real_pwd`/Ogre" +MYGUI_SDK="`real_pwd`/MyGUI" -add_cmake_opts -DOGRE_SDK="$OGRE_SDK" +add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ + -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ + -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" + +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="_d" +else + SUFFIX="" +fi +add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll cd $DEPS echo Done. -# MyGUI -printf "MyGUI 3.2.2... " -cd ../build_$BITS/deps +# Ogre +printf "Ogre 1.9... " +cd $DEPS_INSTALL -eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP -mv MyGUI-3.2.2-win$BITS MyGUI +if [ -d Ogre ]; then + printf "Exists. (No version check) " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf Ogre + eval 7z x -y $DEPS/Ogre-1.9-win$BITS.7z $STRIP + mv Ogre-1.9-win$BITS Ogre +fi -MYGUI_SDK="`real_pwd`/MyGUI" +OGRE_SDK="`real_pwd`/Ogre" -add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ - -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" +add_cmake_opts -DOGRE_SDK="$OGRE_SDK" + +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="_d" +else + SUFFIX="" +fi + +add_runtime_dlls `pwd`/Ogre/bin/$CONFIGURATION/cg.dll \ + `pwd`/Ogre/bin/$CONFIGURATION/{OgreMain,OgreOverlay,Plugin_CgProgramManager,Plugin_ParticleFX,RenderSystem_Direct3D9,RenderSystem_GL}$SUFFIX.dll cd $DEPS @@ -307,21 +414,33 @@ echo Done. # OpenAL printf "OpenAL-Soft 1.16.0... " -eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP +if [ -d openal-soft-1.16.0-bin ]; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf openal-soft-1.16.0-bin + eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP +fi OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" -add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include" \ +add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + + echo Done. # Qt printf "Qt 4.8.6... " -cd ../build_$BITS/deps +cd $DEPS_INSTALL -eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP -mv qt-4.8.6-* Qt +if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf Qt + eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP + mv qt-4.8.6-* Qt +fi QT_SDK="`real_pwd`/Qt" @@ -330,13 +449,26 @@ eval qtbinpatcher.exe $STRIP add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" +if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d4" +else + SUFFIX="4" +fi +add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + cd $DEPS echo Done. # SDL2 printf "SDL 2.0.3... " -eval 7z x -y SDL2-2.0.3.zip $STRIP + +if [ -d SDL2-2.0.3 ]; then + printf "Exists. " +elif [ -z $SKIP_EXTRACT ]; then + rm -rf SDL2-2.0.3 + eval 7z x -y SDL2-2.0.3.zip $STRIP +fi SDL_SDK="`real_pwd`/SDL2-2.0.3" add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ @@ -344,23 +476,59 @@ add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" +add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll + cd $DEPS echo Done. echo -cd ../build_$BITS +cd $DEPS_INSTALL/.. -echo "Building OpenMW..." +echo "Setting up OpenMW build..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ -DBUILD_MYGUI_PLUGIN=no \ - -DBUILD_OPENCS=no \ - -DBUILD_WIZARD=no \ - -DBUILD_ESSIMPORTER=no \ - -DBUILD_LAUNCHER=no \ - -DBUILD_MWINIIMPORTER=no + -DOPENMW_MP_BUILD=yes + +if [ -z $APPVEYOR ]; then + echo " (Outside of AppVeyor, doing full build.)" +else + case $STEP in + components ) + echo " Subproject: Components." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + rm -rf components + ;; + openmw ) + echo " Subproject: OpenMW." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENCS=no \ + -DBUILD_WIZARD=no + ;; + opencs ) + echo " Subproject: OpenCS." + add_cmake_opts -DBUILD_ESSIMPORTER=no \ + -DBUILD_LAUNCHER=no \ + -DBUILD_MWINIIMPORTER=no \ + -DBUILD_OPENMW=no \ + -DBUILD_WIZARD=no + ;; + misc ) + echo " Subproject: Misc." + add_cmake_opts -DBUILD_OPENCS=no \ + -DBUILD_OPENMW=no + ;; + esac +fi if [ -z $VERBOSE ]; then printf " Configuring... " @@ -378,4 +546,11 @@ fi echo +echo "Copying Runtime DLLs..." +mkdir -p $CONFIGURATION +for DLL in $RUNTIME_DLLS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/ +done + exit $RET From 9b09c7bbb0e666aa29c40cfa74db0b2cd654cba0 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 25 Jun 2015 09:02:30 +0200 Subject: [PATCH 015/765] Test the pre-existing Qt 5.4.1 install on Appveyor --- CI/before_script.msvc.sh | 83 +++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index ae74146bc2..f3db640200 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -224,9 +224,11 @@ if [ -z $SKIP_DOWNLOAD ]; then Ogre-1.9-win$BITS.7z # Qt - download "Qt 4.8.6" \ - http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ - qt$BITS-4.8.6.7z + if [ -z $APPVEYOR ]; then + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + fi # SDL2 download "SDL 2.0.3" \ @@ -281,6 +283,7 @@ else -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" fi + # Bullet printf "Bullet 2.83.5... " cd $DEPS_INSTALL @@ -306,6 +309,7 @@ cd $DEPS echo Done. + # FFmpeg printf "FFmpeg 2.5.2... " cd $DEPS_INSTALL @@ -349,6 +353,7 @@ cd $DEPS echo Done. + # MyGUI printf "MyGUI 3.2.2... " cd $DEPS_INSTALL @@ -383,6 +388,7 @@ cd $DEPS echo Done. + # Ogre printf "Ogre 1.9... " cd $DEPS_INSTALL @@ -412,6 +418,7 @@ cd $DEPS echo Done. + # OpenAL printf "OpenAL-Soft 1.16.0... " if [ -d openal-soft-1.16.0-bin ]; then @@ -426,39 +433,51 @@ OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" - - echo Done. + # Qt -printf "Qt 4.8.6... " -cd $DEPS_INSTALL +if [ -z $APPVEYOR ]; then + printf "Qt 4.8.6... " + cd $DEPS_INSTALL -if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf Qt - eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP - mv qt-4.8.6-* Qt -fi + if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Qt + eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP + mv qt-4.8.6-* Qt + fi -QT_SDK="`real_pwd`/Qt" + QT_SDK="`real_pwd`/Qt" -cd $QT_SDK -eval qtbinpatcher.exe $STRIP + cd $QT_SDK + eval qtbinpatcher.exe $STRIP -add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" -if [ $CONFIGURATION == "Debug" ]; then - SUFFIX="d4" + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d4" + else + SUFFIX="4" + fi + add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + + cd $DEPS + + echo Done. else - SUFFIX="4" + echo "Using Appveyor Qt 5 version." + if [ $PLATFORM == "win32" ]; then + QT_SDK="C:/Qt/5.4/msvc2013_opengl" + else + QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" + fi + + add_cmake_opts -DDESIRED_QT_VERSION=4 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" fi -add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll -cd $DEPS - -echo Done. # SDL2 printf "SDL 2.0.3... " @@ -546,11 +565,13 @@ fi echo -echo "Copying Runtime DLLs..." -mkdir -p $CONFIGURATION -for DLL in $RUNTIME_DLLS; do - echo " `basename $DLL`." - cp "$DLL" $CONFIGURATION/ -done +if [ -z $APPVEYOR ]; then + echo "Copying Runtime DLLs..." + mkdir -p $CONFIGURATION + for DLL in $RUNTIME_DLLS; do + echo " `basename $DLL`." + cp "$DLL" $CONFIGURATION/ + done +fi exit $RET From b93cba9a698e353f0da6d19fed41fcb7bdf773a2 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 25 Jun 2015 18:16:59 +0200 Subject: [PATCH 016/765] Push messages properly --- CI/before_script.msvc.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index f3db640200..2652e9898a 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -51,12 +51,9 @@ run_cmd() { if [ $RET -ne 0 ]; then if [ -z $APPVEYOR ]; then echo "Command $CMD failed, output can be found in `real_pwd`/output.log" - exit $RET else - 7z a output.7z output.log > /dev/null 2>&1 - - appveyor PushArtifact output.7z -FileName $CMD-output.7z - appveyor AddMessage "Command $CMD failed (code $RET), output has been pushed as an artifact." -Category Error + appveyor AddMessage "Command $CMD failed." -Category Error -Details "$CMD $@" + while read in; do appveyor AddMessage "$in" -Category Error; done < output.log fi else rm output.log From 331d87d738f7a23c577b7955a7a72c13e8a9554f Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 25 Jun 2015 19:19:04 +0200 Subject: [PATCH 017/765] Bring over some more OSG changes to the Appveyor scripts, and properly use Qt --- CI/before_script.msvc.sh | 13 +++++++------ appveyor.yml | 27 +++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 2652e9898a..06b14f2fe8 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -52,8 +52,9 @@ run_cmd() { if [ -z $APPVEYOR ]; then echo "Command $CMD failed, output can be found in `real_pwd`/output.log" else - appveyor AddMessage "Command $CMD failed." -Category Error -Details "$CMD $@" - while read in; do appveyor AddMessage "$in" -Category Error; done < output.log + echo + echo "Command $CMD failed;" + cat output.log fi else rm output.log @@ -451,7 +452,8 @@ if [ -z $APPVEYOR ]; then cd $QT_SDK eval qtbinpatcher.exe $STRIP - add_cmake_opts -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + add_cmake_opts -DDESIRED_QT_VERSION=4 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" if [ $CONFIGURATION == "Debug" ]; then SUFFIX="d4" @@ -471,7 +473,7 @@ else QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" fi - add_cmake_opts -DDESIRED_QT_VERSION=4 \ + add_cmake_opts -DDESIRED_QT_VERSION=5 \ -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" fi @@ -505,8 +507,7 @@ echo "Setting up OpenMW build..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ - -DBUILD_MYGUI_PLUGIN=no \ - -DOPENMW_MP_BUILD=yes + -DBUILD_MYGUI_PLUGIN=no if [ -z $APPVEYOR ]; then echo " (Outside of AppVeyor, doing full build.)" diff --git a/appveyor.yml b/appveyor.yml index e46dd3dd8c..a71df21552 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,28 +6,25 @@ platform: configuration: - Debug - - Release + +environment: + matrix: + - STEP: openmw + - STEP: opencs + - STEP: misc matrix: fast_finish: true os: unstable -clone_depth: 5 +clone_depth: 1 cache: - - C:\projects\openmw\deps\Bullet-2.83.4-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Bullet-2.83.4-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\MyGUI-3.2.2-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\MyGUI-3.2.2-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Ogre-1.9-win32.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\Ogre-1.9-win64.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg32-2.5.2.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\OpenAL-Soft-1.16.0.zip -> CI/before_script.msvc.sh - - C:\projects\openmw\deps\SDL2-2.0.3.zip -> CI/before_script.msvc.sh + - C:\projects\openmw\deps\ffmpeg32-2.5.2.7z + - C:\projects\openmw\deps\ffmpeg32-2.5.2-dev.7z + - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z + - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z init: - cmd: bash --version @@ -42,3 +39,5 @@ build_script: before_build: - cmd: bash --login C:\projects\openmw\CI\before_script.msvc.sh + +test: off From 8c6ba01c0846ef28759b1090a692ca322acdd966 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 6 Jul 2015 10:59:40 +0200 Subject: [PATCH 018/765] Fix a few merge issues --- CI/before_script.msvc.sh | 46 +++++----------------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index c5c15008ca..a53b6d7957 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -222,9 +222,11 @@ if [ -z $SKIP_DOWNLOAD ]; then OSG-3.3.8-win$BITS.7z # Qt - download "Qt 4.8.6" \ - http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ - qt$BITS-4.8.6.7z + if [ -z $APPVEYOR ]; then + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + fi # SDL2 download "SDL 2.0.3" \ @@ -557,44 +559,6 @@ else esac fi -if [ -z $APPVEYOR ]; then - echo " (Outside of AppVeyor, doing full build.)" -else - case $STEP in - components ) - echo " Subproject: Components." - add_cmake_opts -DBUILD_ESSIMPORTER=no \ - -DBUILD_LAUNCHER=no \ - -DBUILD_MWINIIMPORTER=no \ - -DBUILD_OPENCS=no \ - -DBUILD_OPENMW=no \ - -DBUILD_WIZARD=no - rm -rf components - ;; - openmw ) - echo " Subproject: OpenMW." - add_cmake_opts -DBUILD_ESSIMPORTER=no \ - -DBUILD_LAUNCHER=no \ - -DBUILD_MWINIIMPORTER=no \ - -DBUILD_OPENCS=no \ - -DBUILD_WIZARD=no - ;; - opencs ) - echo " Subproject: OpenCS." - add_cmake_opts -DBUILD_ESSIMPORTER=no \ - -DBUILD_LAUNCHER=no \ - -DBUILD_MWINIIMPORTER=no \ - -DBUILD_OPENMW=no \ - -DBUILD_WIZARD=no - ;; - misc ) - echo " Subproject: Misc." - add_cmake_opts -DBUILD_OPENCS=no \ - -DBUILD_OPENMW=no - ;; - esac -fi - if [ -z $VERBOSE ]; then printf " Configuring... " else From ad47fb7b518db892c70e769c2f58933a572f926f Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 7 Jul 2015 15:50:40 +0200 Subject: [PATCH 019/765] Fix build script --- CI/before_script.msvc.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 06b14f2fe8..7c544ad54d 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -474,7 +474,8 @@ else fi add_cmake_opts -DDESIRED_QT_VERSION=5 \ - -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ + -DCMAKE_PREFIX_PATH="$QT_SDK" fi @@ -507,7 +508,8 @@ echo "Setting up OpenMW build..." add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_ESMTOOL=no \ - -DBUILD_MYGUI_PLUGIN=no + -DBUILD_MYGUI_PLUGIN=no \ + -DOPENMW_MP_BUILD=on if [ -z $APPVEYOR ]; then echo " (Outside of AppVeyor, doing full build.)" From f8485211a1ea5c00310fdbf7bd7ad0fa5dcd32d6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 7 Jul 2015 15:54:03 +0200 Subject: [PATCH 020/765] Fix merge issue --- CI/before_script.msvc.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index a25e238d01..6543df4d3b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -222,9 +222,11 @@ if [ -z $SKIP_DOWNLOAD ]; then OSG-3.3.8-win$BITS.7z # Qt - download "Qt 4.8.6" \ - http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ - qt$BITS-4.8.6.7z + if [ -z $APPVEYOR ]; then + download "Qt 4.8.6" \ + http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ + qt$BITS-4.8.6.7z + fi # SDL2 download "SDL 2.0.3" \ From a831e89a43cc72c1bd8ffefb7570a7c73be45c38 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 7 Jul 2015 16:21:56 +0200 Subject: [PATCH 021/765] Fix MyGUI include dir, and properly return error codes --- CI/before_script.msvc.sh | 3 +-- CI/build.msvc.sh | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 6543df4d3b..0d72806fb5 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -372,8 +372,7 @@ fi MYGUI_SDK="`real_pwd`/MyGUI" add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include" \ - -DMYGUI_PLATFORM_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" if [ $CONFIGURATION == "Debug" ]; then diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index d426ef90d4..731c51eda0 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -50,6 +50,10 @@ else msbuild OpenMW.sln //t:Build //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" fi -if [ ! -z $PACKAGE ]; then +RET=$? +if [ $RET -eq 0 ] && [ ! -z $PACKAGE ]; then msbuild PACKAGE.vcxproj //t:Build //m:8 -fi \ No newline at end of file + RET=$? +fi + +exit $RET From 27a4af26abdb65e32eed89af1f2ca3d8b6cedc61 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sun, 12 Jul 2015 00:45:42 +0200 Subject: [PATCH 022/765] Cleaning up AppVeyor scripts --- CI/before_script.msvc.sh | 407 ++++++++++++++++++++------------------- appveyor.yml | 31 +-- 2 files changed, 224 insertions(+), 214 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0d72806fb5..d23e3013f5 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -124,6 +124,11 @@ add_runtime_dlls() { RUNTIME_DLLS="$RUNTIME_DLLS $@" } +OSG_PLUGINS="" +add_osg_dlls() { + OSG_PLUGINS="$OSG_PLUGINS $@" +} + if [ -z $PLATFORM ]; then PLATFORM=`uname -m` fi @@ -250,270 +255,274 @@ mkdir -p Build_$BITS/deps cd Build_$BITS/deps DEPS_INSTALL=`pwd` +cd $DEPS echo echo "Extracting dependencies..." # Boost -if [ -z $APPVEYOR ]; then - printf "Boost 1.58.0... " - cd $DEPS_INSTALL +printf "Boost 1.58.0... " +{ + if [ -z $APPVEYOR ]; then + cd $DEPS_INSTALL - BOOST_SDK="`real_pwd`/Boost" + BOOST_SDK="`real_pwd`/Boost" - if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then - printf "Exists. " - elif [ -z $SKIP_EXTRACT ]; then - rm -rf Boost - $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Boost + $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + fi + + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + + echo Done. + else + # Appveyor unstable has all the boost we need already + BOOST_SDK="c:/Libraries/boost" + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ + -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + + echo AppVeyor. fi - - add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" - - cd $DEPS - - echo Done. -else - # Appveyor unstable has all the boost we need already - BOOST_SDK="c:/Libraries/boost" - add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" -fi - +} +cd $DEPS # Bullet printf "Bullet 2.83.5... " -cd $DEPS_INSTALL +{ + cd $DEPS_INSTALL -if [ -d Bullet ]; then - printf "Exists. (No version checking) " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf Bullet - eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP - mv Bullet-2.83.5-win$BITS Bullet -fi + if [ -d Bullet ]; then + printf "Exists. (No version checking) " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Bullet + eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP + mv Bullet-2.83.5-win$BITS Bullet + fi -BULLET_SDK="`real_pwd`/Bullet" -add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ - -DBULLET_COLLISION_LIBRARY="$BULLET_SDK/lib/BulletCollision.lib" \ - -DBULLET_COLLISION_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletCollision_Debug.lib" \ - -DBULLET_DYNAMICS_LIBRARY="$BULLET_SDK/lib/BulletDynamics.lib" \ - -DBULLET_DYNAMICS_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletDynamics_Debug.lib" \ - -DBULLET_MATH_LIBRARY="$BULLET_SDK/lib/LinearMath.lib" \ - -DBULLET_MATH_LIBRARY_DEBUG="$BULLET_SDK/lib/LinearMath_Debug.lib" + BULLET_SDK="`real_pwd`/Bullet" + add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ + -DBULLET_COLLISION_LIBRARY="$BULLET_SDK/lib/BulletCollision.lib" \ + -DBULLET_COLLISION_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletCollision_Debug.lib" \ + -DBULLET_MATH_LIBRARY="$BULLET_SDK/lib/LinearMath.lib" \ + -DBULLET_MATH_LIBRARY_DEBUG="$BULLET_SDK/lib/LinearMath_Debug.lib" + echo Done. +} cd $DEPS -echo Done. - - # FFmpeg printf "FFmpeg 2.5.2... " -cd $DEPS_INSTALL +{ + cd $DEPS_INSTALL -if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf FFmpeg + if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf FFmpeg - eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP - eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP + eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP - mv ffmpeg-2.5.2-win$BITS-shared FFmpeg - cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ - rm -rf ffmpeg-2.5.2-win$BITS-dev -fi + mv ffmpeg-2.5.2-win$BITS-shared FFmpeg + cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ + rm -rf ffmpeg-2.5.2-win$BITS-dev + fi -FFMPEG_SDK="`real_pwd`/FFmpeg" -add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVCODEC_LIBRARIES="$FFMPEG_SDK/lib/avcodec.lib" \ - -DAVDEVICE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVDEVICE_LIBRARIES="$FFMPEG_SDK/lib/avdevice.lib" \ - -DAVFORMAT_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVFORMAT_LIBRARIES="$FFMPEG_SDK/lib/avformat.lib" \ - -DAVUTIL_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVUTIL_LIBRARIES="$FFMPEG_SDK/lib/avutil.lib" \ - -DPOSTPROC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DPOSTPROC_LIBRARIES="$FFMPEG_SDK/lib/postproc.lib" \ - -DSWRESAMPLE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DSWRESAMPLE_LIBRARIES="$FFMPEG_SDK/lib/swresample.lib" \ - -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" + FFMPEG_SDK="`real_pwd`/FFmpeg" + add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVCODEC_LIBRARIES="$FFMPEG_SDK/lib/avcodec.lib" \ + -DAVDEVICE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVDEVICE_LIBRARIES="$FFMPEG_SDK/lib/avdevice.lib" \ + -DAVFORMAT_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVFORMAT_LIBRARIES="$FFMPEG_SDK/lib/avformat.lib" \ + -DAVUTIL_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DAVUTIL_LIBRARIES="$FFMPEG_SDK/lib/avutil.lib" \ + -DPOSTPROC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DPOSTPROC_LIBRARIES="$FFMPEG_SDK/lib/postproc.lib" \ + -DSWRESAMPLE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DSWRESAMPLE_LIBRARIES="$FFMPEG_SDK/lib/swresample.lib" \ + -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ + -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" -add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll + add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll -if [ $BITS -eq 32 ]; then - add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" -fi + if [ $BITS -eq 32 ]; then + add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" + fi + echo Done. +} cd $DEPS -echo Done. - - # MyGUI printf "MyGUI 3.2.2... " -cd $DEPS_INSTALL +{ + cd $DEPS_INSTALL -if [ -d MyGUI ] && \ - grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null -then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf MyGUI - eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP - mv MyGUI-3.2.2-win$BITS MyGUI -fi + if [ -d MyGUI ] && \ + grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ + grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf MyGUI + eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP + mv MyGUI-3.2.2-win$BITS MyGUI + fi -MYGUI_SDK="`real_pwd`/MyGUI" + MYGUI_SDK="`real_pwd`/MyGUI" -add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ - -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" + add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ + -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ + -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" -if [ $CONFIGURATION == "Debug" ]; then - SUFFIX="_d" -else - SUFFIX="" -fi -add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="_d" + else + SUFFIX="" + fi + add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll + echo Done. +} cd $DEPS -echo Done. - - # OpenAL printf "OpenAL-Soft 1.16.0... " -if [ -d openal-soft-1.16.0-bin ]; then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf openal-soft-1.16.0-bin - eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP -fi +{ + if [ -d openal-soft-1.16.0-bin ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf openal-soft-1.16.0-bin + eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP + fi -OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" + OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" -add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ - -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" - -echo Done. + add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ + -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + echo Done. +} +cd $DEPS # OSG printf "OSG 3.3.8... " -cd $DEPS_INSTALL +{ + cd $DEPS_INSTALL -if [ -d OSG ] && \ - grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ - grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ - grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null -then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf OSG - eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP - mv OSG-3.3.8-win$BITS OSG -fi + if [ -d OSG ] && \ + grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null + then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf OSG + eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP + mv OSG-3.3.8-win$BITS OSG + fi -OSG_SDK="`real_pwd`/OSG" + OSG_SDK="`real_pwd`/OSG" -add_cmake_opts -DOSG_DIR="$OSG_SDK" + add_cmake_opts -DOSG_DIR="$OSG_SDK" -if [ $CONFIGURATION == "Debug" ]; then - SUFFIX="d" -else - SUFFIX="" -fi -add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \ - `pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d" + else + SUFFIX="" + fi -OSG_PLUGINS="" -add_osg_dlls() { - OSG_PLUGINS="$OSG_PLUGINS $@" + add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \ + `pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll + + add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll + + echo Done. } - -add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll - cd $DEPS -echo Done. - - # Qt if [ -z $APPVEYOR ]; then printf "Qt 4.8.6... " - cd $DEPS_INSTALL - - if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then - printf "Exists. " - elif [ -z $SKIP_EXTRACT ]; then - rm -rf Qt - eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP - mv qt-4.8.6-* Qt - fi - - QT_SDK="`real_pwd`/Qt" - - cd $QT_SDK - eval qtbinpatcher.exe $STRIP - - add_cmake_opts -DDESIRED_QT_VERSION=4 \ - -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" - - if [ $CONFIGURATION == "Debug" ]; then - SUFFIX="d4" - else - SUFFIX="4" - fi - add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll - - cd $DEPS - - echo Done. else - echo "Using Appveyor Qt 5 version." - if [ $PLATFORM == "win32" ]; then - QT_SDK="C:/Qt/5.4/msvc2013_opengl" - else - QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" - fi - - add_cmake_opts -DDESIRED_QT_VERSION=5 \ - -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ - -DCMAKE_PREFIX_PATH="$QT_SDK" + printf "Qt 5.4... " fi +{ + if [ -z $APPVEYOR ]; then + cd $DEPS_INSTALL + QT_SDK="`real_pwd`/Qt" + if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf Qt + eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP + mv qt-4.8.6-* Qt + ( + cd $QT_SDK + eval qtbinpatcher.exe $STRIP + ) + fi + + cd $QT_SDK + + add_cmake_opts -DDESIRED_QT_VERSION=4 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + + if [ $CONFIGURATION == "Debug" ]; then + SUFFIX="d4" + else + SUFFIX="4" + fi + + add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + + echo Done. + else + if [ $BITS -eq 32 ]; then + QT_SDK="C:/Qt/5.4/msvc2013_opengl" + else + QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" + fi + + add_cmake_opts -DDESIRED_QT_VERSION=5 \ + -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ + -DCMAKE_PREFIX_PATH="$QT_SDK" + fi +} +cd $DEPS # SDL2 printf "SDL 2.0.3... " +{ + if [ -d SDL2-2.0.3 ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf SDL2-2.0.3 + eval 7z x -y SDL2-2.0.3.zip $STRIP + fi -if [ -d SDL2-2.0.3 ]; then - printf "Exists. " -elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.3 - eval 7z x -y SDL2-2.0.3.zip $STRIP -fi + SDL_SDK="`real_pwd`/SDL2-2.0.3" + add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ + -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ + -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" -SDL_SDK="`real_pwd`/SDL2-2.0.3" -add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ - -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ - -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ - -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll -add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll + echo Done. +} -cd $DEPS - -echo Done. -echo cd $DEPS_INSTALL/.. +echo echo "Setting up OpenMW build..." add_cmake_opts -DBUILD_BSATOOL=no \ @@ -521,8 +530,8 @@ add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_MYGUI_PLUGIN=no \ -DOPENMW_MP_BUILD=on -if [ -z $APPVEYOR ]; then - echo " (Outside of AppVeyor, doing full build.)" +if [ -z $CI ]; then + echo " (Outside of CI, doing full build.)" else case $STEP in components ) @@ -533,7 +542,6 @@ else -DBUILD_OPENCS=no \ -DBUILD_OPENMW=no \ -DBUILD_WIZARD=no - rm -rf components ;; openmw ) echo " Subproject: OpenMW." @@ -575,7 +583,8 @@ fi echo -if [ -z $APPVEYOR ]; then +# NOTE: Disable this when/if we want to run test cases +if [ -z $CI ]; then echo "Copying Runtime DLLs..." mkdir -p $CONFIGURATION for DLL in $RUNTIME_DLLS; do diff --git a/appveyor.yml b/appveyor.yml index 926aaff689..d5ad134306 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,17 +1,22 @@ version: "{build}" +branches: + only: + - appveyor + platform: - Win32 - x64 -configuration: - - Debug +configuration: Debug environment: matrix: - - STEP: openmw - - STEP: opencs - - STEP: misc + - STEP: misc +# - STEP: components # misc builds this too +# Build takes too long for these, ignore for now +# - STEP: openmw +# - STEP: opencs matrix: fast_finish: true @@ -32,18 +37,14 @@ cache: - C:\projects\openmw\deps\ffmpeg64-2.5.2.7z - C:\projects\openmw\deps\ffmpeg64-2.5.2-dev.7z -init: - - cmd: bash --version - - cmd: cmake --version - - cmd: msbuild /version - - cmd: echo. - clone_folder: C:\projects\openmw -build_script: - - cmd: bash --login C:\projects\openmw\CI\build.msvc.sh - before_build: - - cmd: bash --login C:\projects\openmw\CI\before_script.msvc.sh + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh + +build_script: + - cmd: if %PLATFORM%==Win32 set build=Build_32 + - cmd: if %PLATFORM%==x64 set build=Build_64 + - cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test: off From 90af7060ea2c5a17e0fdbcf00d7e926a0e232a86 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 14 Jul 2015 16:26:32 +0200 Subject: [PATCH 023/765] Forgot an echo --- CI/before_script.msvc.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index d23e3013f5..cd47cbf8d4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -495,6 +495,8 @@ fi add_cmake_opts -DDESIRED_QT_VERSION=5 \ -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" + + echo AppVeyor. fi } cd $DEPS From fec08ef73e286e3749b67b9fa768e7e773a180b0 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Sep 2015 15:29:08 +0200 Subject: [PATCH 024/765] Run unity build --- CI/before_script.msvc.sh | 7 +++++++ appveyor.yml | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index cd47cbf8d4..756653fe97 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -16,6 +16,9 @@ while [ $# -gt 0 ]; do -k ) KEEP=true ;; + + -u ) + UNITY_BUILD=true ;; * ) echo "Unknown arg $ARG." @@ -162,6 +165,10 @@ case $PLATFORM in ;; esac +if ! [ -z $UNITY_BUILD ]; then + add_cmake_opts "-DOPENMW_UNITY_BUILD=True" +fi + case $CONFIGURATION in debug|Debug|DEBUG ) CONFIGURATION=Debug diff --git a/appveyor.yml b/appveyor.yml index d5ad134306..e89a54e3b9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,7 @@ environment: - STEP: misc # - STEP: components # misc builds this too # Build takes too long for these, ignore for now -# - STEP: openmw + - STEP: openmw # - STEP: opencs matrix: @@ -40,7 +40,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From 33175b44b827601c070776891a679eda617cf8b0 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Sep 2015 16:02:03 +0200 Subject: [PATCH 025/765] Disable SDL main library --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 756653fe97..bc8965066f 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -520,8 +520,8 @@ printf "SDL 2.0.3... " SDL_SDK="`real_pwd`/SDL2-2.0.3" add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ - -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ - -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ + -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll From 7df115d61bade2750fb22a1e0eaeb473d07aebbd Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Sep 2015 16:21:40 +0200 Subject: [PATCH 026/765] Just run OpenMW builds for now --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e89a54e3b9..ea508abd6c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,7 @@ configuration: Debug environment: matrix: - - STEP: misc +# - STEP: misc # - STEP: components # misc builds this too # Build takes too long for these, ignore for now - STEP: openmw From 2e174fd1b5981103fc317bcca83a06748c6b02c1 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Sep 2015 19:59:17 +0200 Subject: [PATCH 027/765] Readd SDL2 main --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index bc8965066f..756653fe97 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -520,8 +520,8 @@ printf "SDL 2.0.3... " SDL_SDK="`real_pwd`/SDL2-2.0.3" add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ - -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" \ - -DSDL2_LIBRARY_ONLY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ + -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll From 4ddb2feebb1b9a3fb5fc813047ab110c99605ac4 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 28 Oct 2015 17:35:16 +0100 Subject: [PATCH 028/765] Let's try building everything, see how far we get --- CI/before_script.msvc.sh | 3 +++ appveyor.yml | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 756653fe97..37905fc4c2 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -573,6 +573,9 @@ else add_cmake_opts -DBUILD_OPENCS=no \ -DBUILD_OPENMW=no ;; + * ) + echo " Building everything." + ;; esac fi diff --git a/appveyor.yml b/appveyor.yml index ea508abd6c..651f41783e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,12 +10,12 @@ platform: configuration: Debug -environment: - matrix: +#environment: +# matrix: # - STEP: misc # - STEP: components # misc builds this too # Build takes too long for these, ignore for now - - STEP: openmw +# - STEP: openmw # - STEP: opencs matrix: From f3187b17c83d0196b2b580b0122a938743f28770 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 23 Dec 2015 18:38:53 +0100 Subject: [PATCH 029/765] Update OpenAL soft link, get qtbinpatcher to run --- CI/before_script.msvc.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 37905fc4c2..50fd13e4e2 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -225,7 +225,7 @@ if [ -z $SKIP_DOWNLOAD ]; then # OpenAL download "OpenAL-Soft 1.16.0" \ - http://kcat.strangesoft.net/openal-soft-1.16.0-bin.zip \ + http://kcat.strangesoft.net/openal-binaries/openal-soft-1.16.0-bin.zip \ OpenAL-Soft-1.16.0.zip # OSG @@ -472,10 +472,8 @@ fi rm -rf Qt eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP mv qt-4.8.6-* Qt - ( - cd $QT_SDK - eval qtbinpatcher.exe $STRIP - ) + cd Qt + eval ./qtbinpatcher.exe $STRIP fi cd $QT_SDK From 5743c839a8bd40fa9336c7dacdaae4c59b1ef7ee Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 23 Dec 2015 18:51:04 +0100 Subject: [PATCH 030/765] Why is this breaking on AppVeyor? --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 651f41783e..be8f2b12bf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,7 +40,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -v build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From c226d015aca698cb8ac95f8ee6571f36c2946248 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 23 Dec 2015 18:59:41 +0100 Subject: [PATCH 031/765] AppVeyor doesn't pass the platform env variable? --- CI/before_script.msvc.sh | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 50fd13e4e2..3cb024ddaf 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -40,7 +40,7 @@ else cd $APPVEYOR_BUILD_FOLDER VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)" - appveyor UpdateBuild -Version "$VERSION" + appveyor UpdateBuild -Version "$VERSION" > /dev/null & fi run_cmd() { diff --git a/appveyor.yml b/appveyor.yml index be8f2b12bf..b137d4850b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,7 +40,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -v + - cmd: sh PLATFORM=%PLATFORM% %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From c8145a1b917d81639577cfb7c6018d0d786b7b73 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 23 Dec 2015 19:32:43 +0100 Subject: [PATCH 032/765] Let's see if we can't get AppVeyor to work --- CI/before_script.msvc.sh | 8 ++++++++ appveyor.yml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 3cb024ddaf..51734ceb37 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -20,6 +20,14 @@ while [ $# -gt 0 ]; do -u ) UNITY_BUILD=true ;; + -p ) + PLATFORM=$1 + shift ;; + + -c ) + CONFIGURATION=$1 + shift ;; + * ) echo "Unknown arg $ARG." exit 1 ;; diff --git a/appveyor.yml b/appveyor.yml index b137d4850b..46241bf29f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,7 +40,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh PLATFORM=%PLATFORM% %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -p %PLATFORM% build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From 1dbc1e67d8cd4f2807792bac7aa3e83d5fa274b3 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 02:41:35 +0100 Subject: [PATCH 033/765] Possible Qt5 link fix for AppVeyor --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e966a1635..d75d044b12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,8 +99,8 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() -# We probably support older versions than this. -cmake_minimum_required(VERSION 2.6) +# To make Qt5 happy and allow linking QtMain on Windows. +cmake_minimum_required(VERSION 2.8.11) # Sound setup unset(FFMPEG_LIBRARIES CACHE) From 2142d0d2d65c40155be00e49578eea5e60a25080 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 02:48:00 +0100 Subject: [PATCH 034/765] Make Qt5 happy --- apps/launcher/CMakeLists.txt | 3 --- apps/opencs/CMakeLists.txt | 3 --- apps/wizard/CMakeLists.txt | 3 --- 3 files changed, 9 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 274239fb0c..4d2dc6ac4b 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -103,9 +103,6 @@ if (DESIRED_QT_VERSION MATCHES 4) endif(WIN32) else() qt5_use_modules(openmw-launcher Widgets Core) - if (WIN32) - target_link_libraries(Qt5::WinMain) - endif() endif() if (BUILD_WITH_CODE_COVERAGE) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6af04e8fc2..a8ed683282 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -224,9 +224,6 @@ if (DESIRED_QT_VERSION MATCHES 4) endif() else() qt5_use_modules(openmw-cs Widgets Core Network OpenGL) - if (WIN32) - target_link_libraries(Qt5::WinMain) - endif() endif() if (WIN32) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 89438640c1..4be5fb2b76 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -129,9 +129,6 @@ if (DESIRED_QT_VERSION MATCHES 4) endif() else() qt5_use_modules(openmw-wizard Widgets Core) - if (WIN32) - target_link_libraries(Qt5::WinMain) - endif() endif() if (OPENMW_USE_UNSHIELD) From 93ee8f1991beeeb8a1822164df502f2cf940001e Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 11:33:59 +0100 Subject: [PATCH 035/765] Allow for earlier CMake on Qt4 --- CMakeLists.txt | 57 +++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 906c87db20..509988fef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,8 +111,37 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() -# To make Qt5 happy and allow linking QtMain on Windows. -cmake_minimum_required(VERSION 2.8.11) +if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) + set(USE_QT FALSE) +else() + set(USE_QT TRUE) +endif() + +# Dependencies +if (USE_QT) + set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") + set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) + message(STATUS "Using Qt${DESIRED_QT_VERSION}") + + if (DESIRED_QT_VERSION MATCHES 4) + find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork QtOpenGL) + else() + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Core REQUIRED) + find_package(Qt5Network REQUIRED) + find_package(Qt5OpenGL REQUIRED) + # Instruct CMake to run moc automatically when needed. + #set(CMAKE_AUTOMOC ON) + endif() +endif() + +if (USE_QT AND DESIRED_QT_VERSION MATCHES 5) + # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. + cmake_minimum_required(VERSION 2.8.11) +else() + # We probably support older versions than this. + cmake_minimum_required(VERSION 2.6) +endif() # Sound setup unset(FFMPEG_LIBRARIES CACHE) @@ -167,30 +196,6 @@ if (OPENGL_ES) add_definitions(-DOPENGL_ES) endif(OPENGL_ES) -if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) - set(USE_QT FALSE) -else() - set(USE_QT TRUE) -endif() - -# Dependencies -if (USE_QT) - set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") - set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) - message(STATUS "Using Qt${DESIRED_QT_VERSION}") - - if (DESIRED_QT_VERSION MATCHES 4) - find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork QtOpenGL) - else() - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Core REQUIRED) - find_package(Qt5Network REQUIRED) - find_package(Qt5OpenGL REQUIRED) - # Instruct CMake to run moc automatically when needed. - #set(CMAKE_AUTOMOC ON) - endif() -endif() - # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) From 09ec622f66d0d4e970c8a32165161d7aa4acdb94 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 11:42:12 +0100 Subject: [PATCH 036/765] Remove unused environment, only do 32-bit --- appveyor.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 46241bf29f..631cae63ac 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,18 +6,10 @@ branches: platform: - Win32 - - x64 +# - x64 configuration: Debug -#environment: -# matrix: -# - STEP: misc -# - STEP: components # misc builds this too -# Build takes too long for these, ignore for now -# - STEP: openmw -# - STEP: opencs - matrix: fast_finish: true From f422fe49f8b2f07bec6e4fcd9950fd15ac9bb7f3 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 18:27:57 +0100 Subject: [PATCH 037/765] Slight improvement to the build setup --- CI/before_script.msvc.sh | 111 +++++++++++++++++++++++++++++---------- appveyor.yml | 3 +- 2 files changed, 86 insertions(+), 28 deletions(-) mode change 100644 => 100755 CI/before_script.msvc.sh diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh old mode 100644 new mode 100755 index 51734ceb37..5aa8d3e9d5 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1,42 +1,86 @@ #!/bin/bash while [ $# -gt 0 ]; do - ARG=$1 + ARGSTR=$1 shift - case $ARG in - -v ) - VERBOSE=true ;; + if [ ${ARGSTR:0:1} != "-" ]; then + echo "Unknown argument $ARGSTR" + echo "Try '$0 -h'" + exit 1 + fi - -d ) - SKIP_DOWNLOAD=true ;; + for (( i=1; i<${#ARGSTR}; i++ )); do + ARG=${ARGSTR:$i:1} + case $ARG in + V ) + VERBOSE=true ;; - -e ) - SKIP_EXTRACT=true ;; + v ) + VS_VERSION=$1 + shift ;; - -k ) - KEEP=true ;; - - -u ) - UNITY_BUILD=true ;; + d ) + SKIP_DOWNLOAD=true ;; - -p ) - PLATFORM=$1 - shift ;; + e ) + SKIP_EXTRACT=true ;; - -c ) - CONFIGURATION=$1 - shift ;; + k ) + KEEP=true ;; - * ) - echo "Unknown arg $ARG." - exit 1 ;; - esac + u ) + UNITY_BUILD=true ;; + + p ) + PLATFORM=$1 + shift ;; + + c ) + CONFIGURATION=$1 + shift ;; + + h ) + cat < + Set the configuration, can also be set with environment variable CONFIGURATION. + -d + Skip checking the downloads. + -e + Skip extracting dependencies. + -h + Show this message. + -k + Keep the old build directory, default is to delete it. + -p + Set the build platform, can also be set with environment variable PLATFORM. + -u + Configure for unity builds. + -v <2013/2015> + Choose the Visual Studio version to use. + -V + Run verbosely +EOF + exit 0 + ;; + + * ) + echo "Unknown argument $ARG." + echo "Try '$0 -h'" + exit 1 ;; + esac + done done if [ -z $VERBOSE ]; then STRIP="> /dev/null 2>&1" fi +if [ -z $VS_VERSION ]; then + VS_VERSION="2013" +fi if [ -z $APPVEYOR ]; then echo "Running prebuild outside of Appveyor." @@ -148,14 +192,27 @@ if [ -z $CONFIGURATION ]; then CONFIGURATION="Debug" fi +case $VS_VERSION in + 14|2015 ) + GENERATOR="Visual Studio 14 2015" + XP_TOOLSET="v140_xp" + ;; + +# 12|2013| + * ) + GENERATOR="Visual Studio 12 2013" + XP_TOOLSET="v120_xp" + ;; +esac + case $PLATFORM in x64|x86_64|x86-64|win64|Win64 ) ARCHNAME=x86-64 ARCHSUFFIX=64 BITS=64 - BASE_OPTS="-G\"Visual Studio 12 2013 Win64\"" - add_cmake_opts "-G\"Visual Studio 12 2013 Win64\"" + BASE_OPTS="-G\"$GENERATOR Win64\"" + add_cmake_opts "-G\"$GENERATOR Win64\"" ;; x32|x86|i686|i386|win32|Win32 ) @@ -163,8 +220,8 @@ case $PLATFORM in ARCHSUFFIX=86 BITS=32 - BASE_OPTS="-G\"Visual Studio 12 2013\" -Tv120_xp" - add_cmake_opts "-G\"Visual Studio 12 2013\"" -Tv120_xp + BASE_OPTS="-G\"$GENERATOR\" -T$XP_TOOLSET" + add_cmake_opts "-G\"$GENERATOR\"" -T$XP_TOOLSET ;; * ) diff --git a/appveyor.yml b/appveyor.yml index 631cae63ac..0667e0ee09 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,8 @@ matrix: os: unstable -clone_depth: 1 +shallow_clone: true +#clone_depth: 1 cache: - C:\projects\openmw\deps\Bullet-2.83.5-win32.7z From d32358558e91b889cebb79873a5a4bf8b41ac63b Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 24 Dec 2015 22:39:07 +0100 Subject: [PATCH 038/765] We actually want the commit hash, so no zips --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0667e0ee09..28df364df2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,8 +15,8 @@ matrix: os: unstable -shallow_clone: true -#clone_depth: 1 +#shallow_clone: true +clone_depth: 1 cache: - C:\projects\openmw\deps\Bullet-2.83.5-win32.7z From 1ac091e5b5c0200356bdb50e3d23fe606f834703 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sun, 27 Dec 2015 20:00:52 +0100 Subject: [PATCH 039/765] Quick appveyor.yml cleanup --- appveyor.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 28df364df2..1bc0d3ca69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,8 @@ version: "{build}" branches: only: + - master + - /openmw-.*$/ - appveyor platform: @@ -13,9 +15,11 @@ configuration: Debug matrix: fast_finish: true +# For the Qt, Boost, CMake, etc installs os: unstable -#shallow_clone: true +# We want the git revision for versioning, +# so shallow clones don't work. clone_depth: 1 cache: @@ -41,3 +45,10 @@ build_script: - cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test: off + +#notifications: +# - provider: Email +# to: +# - +# on_build_failure: true +# on_build_status_changed: true From d5196ac4fc3ea94646023e1ee0c59a9eeac39c55 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sun, 27 Dec 2015 20:05:37 +0100 Subject: [PATCH 040/765] Let's throw in a build badge too --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f353cd76ef..585805368f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OpenMW ====== -[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![Build Status](https://img.shields.io/travis/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/ace13/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) 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. From 05335491ac82a78f1fe050f3b84478df0e61442d Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 28 Dec 2015 05:42:21 +0100 Subject: [PATCH 041/765] Build without unity builds, how far will we get? --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1bc0d3ca69..3e3de6780c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -p %PLATFORM% + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -p %PLATFORM% build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From 42e3ff6f962dc68296b12ee693871d7f9201cc07 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 28 Dec 2015 09:01:08 +0100 Subject: [PATCH 042/765] Revert "Build without unity builds, how far will we get?" This reverts commit 05335491ac82a78f1fe050f3b84478df0e61442d. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3e3de6780c..1bc0d3ca69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ cache: clone_folder: C:\projects\openmw before_build: - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -p %PLATFORM% + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -u -p %PLATFORM% build_script: - cmd: if %PLATFORM%==Win32 set build=Build_32 From 7f7e8c63bf645e44930ef788b88a7f554de672ba Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 31 Dec 2015 00:37:47 +0100 Subject: [PATCH 043/765] Correct path to gamecontrollerdb.txt (Fixes #3112) --- apps/openmw/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b43fd2f530..3fcd46f7ca 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -474,8 +474,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // find correct path to the game controller bindings - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.cfg"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.cfg"; + const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; + const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; From 6fde02ea426ee583ece2c3d22a4d6717adcd9eb2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Dec 2015 15:22:19 +0100 Subject: [PATCH 044/765] LocalScripts: initialize mIter --- apps/openmw/mwworld/localscripts.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 46d0b3cc22..2004a2ff39 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -61,7 +61,10 @@ namespace } -MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {} +MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) +{ + mIter = mScripts.end(); +} void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) { From 0333c4004733d3df5ed2fe81cc95b28dcbfe781b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 1 Jan 2016 02:59:14 +0100 Subject: [PATCH 045/765] Fix typo in SameRace dialogue function (function was inverted, Fixes #3116) --- apps/openmw/mwdialogue/filter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 43d979ea28..ce07593e6a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -478,7 +478,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameRace: - return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); + return Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); case SelectWrapper::Function_SameFaction: From a9f0f30bb879a2197d9abc9f65d74673bead6e51 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 1 Jan 2016 23:55:17 +0100 Subject: [PATCH 046/765] Reject conditions testing agaist Choice when not currently in a choice (Fixes #3117) --- apps/openmw/mwdialogue/filter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index ce07593e6a..69df38ccfa 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -155,10 +155,13 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) - // If the actor is a creature, we do not test the conditions applicable - // only to NPCs. + // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; + if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) + // If not currently in a choice, we reject all conditions that test against choices. + return false; + switch (select.getType()) { case SelectWrapper::Type_None: return true; From 1905f0bf2dd8bb9000c033f89564ff3cac14f7ed Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 2 Jan 2016 00:49:53 +0100 Subject: [PATCH 047/765] Add support for placing BodyParts in a cell (Bug #3118) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/bodypart.cpp | 52 +++++++++++++++++++++++++++++++ apps/openmw/mwclass/bodypart.hpp | 31 ++++++++++++++++++ apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwworld/cellstore.cpp | 7 +++++ apps/openmw/mwworld/cellstore.hpp | 10 ++++++ apps/openmw/mwworld/esmstore.cpp | 3 +- apps/openmw/mwworld/manualref.cpp | 1 + 8 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwclass/bodypart.cpp create mode 100644 apps/openmw/mwclass/bodypart.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0113fed022..02cf6c87dd 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -75,7 +75,7 @@ add_openmw_dir (mwphysics add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door - ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor + ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart ) add_openmw_dir (mwmechanics diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp new file mode 100644 index 0000000000..be2b927cc2 --- /dev/null +++ b/apps/openmw/mwclass/bodypart.cpp @@ -0,0 +1,52 @@ +#include "bodypart.hpp" + +#include "../mwrender/renderinginterface.hpp" +#include "../mwrender/objects.hpp" + +#include "../mwworld/cellstore.hpp" + +namespace MWClass +{ + + MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void BodyPart::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string &model, MWRender::RenderingInterface &renderingInterface) const + { + if (!model.empty()) { + renderingInterface.getObjects().insertModel(ptr, model); + } + } + + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const + { + return std::string(); + } + + void BodyPart::registerSelf() + { + boost::shared_ptr instance (new BodyPart); + + registerClass (typeid (ESM::BodyPart).name(), instance); + } + + std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + const std::string &model = ref->mBase->mModel; + if (!model.empty()) { + return "meshes\\" + model; + } + return ""; + } + +} diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp new file mode 100644 index 0000000000..79c7c860de --- /dev/null +++ b/apps/openmw/mwclass/bodypart.hpp @@ -0,0 +1,31 @@ +#ifndef GAME_MWCLASS_BODYPART_H +#define GAME_MWCLASS_BODYPART_H + +#include "../mwworld/class.hpp" + +namespace MWClass +{ + + class BodyPart : public MWWorld::Class + { + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; + + public: + + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; + ///< Add reference into a cell for rendering + + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; + + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; + ///< \return name (the one that is to be presented to the user; not the internal one); + /// can return an empty string. + + static void registerSelf(); + + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + }; + +} + +#endif diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index c303b23af0..a552dfebf0 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -20,6 +20,7 @@ #include "probe.hpp" #include "repair.hpp" #include "static.hpp" +#include "bodypart.hpp" namespace MWClass { @@ -45,5 +46,6 @@ namespace MWClass Probe::registerSelf(); Repair::registerSelf(); Static::registerSelf(); + BodyPart::registerSelf(); } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 08f272ea2a..5b53643e89 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -591,6 +591,7 @@ namespace MWWorld case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; + case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; @@ -659,6 +660,7 @@ namespace MWWorld writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); + writeReferenceCollection (writer, mBodyParts); for (MovedRefTracker::const_iterator it = mMovedToAnotherCell.begin(); it != mMovedToAnotherCell.end(); ++it) { @@ -794,6 +796,11 @@ namespace MWWorld readReferenceCollection (reader, mWeapons, cref, contentFileMap); break; + case ESM::REC_BODY: + + readReferenceCollection (reader, mBodyParts, cref, contentFileMap); + break; + default: throw std::runtime_error ("unknown type in cell reference section"); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 27fe9ec036..faba76262b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld @@ -97,6 +98,7 @@ namespace MWWorld CellRefList mRepairs; CellRefList mStatics; CellRefList mWeapons; + CellRefList mBodyParts; typedef std::map MovedRefTracker; // References owned by a different cell that have been moved here. @@ -152,6 +154,7 @@ namespace MWWorld forEachImp (visitor, mRepairs) && forEachImp (visitor, mStatics) && forEachImp (visitor, mWeapons) && + forEachImp (visitor, mBodyParts) && forEachImp (visitor, mCreatures) && forEachImp (visitor, mNpcs) && forEachImp (visitor, mCreatureLists); @@ -518,6 +521,13 @@ namespace MWWorld return mWeapons; } + template<> + inline CellRefList& CellStore::get() + { + mHasState = true; + return mBodyParts; + } + bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1882c6e1aa..7a1f222c78 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -19,7 +19,8 @@ static bool isCacheableRecord(int id) id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA || id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI || id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ || - id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP) + id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP || + id == ESM::REC_BODY) { return true; } diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index d6e40ad09e..c683f7e036 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -54,6 +54,7 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const std::string& case ESM::REC_REPA: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_STAT: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_WEAP: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_BODY: create(store.get(), lowerName, mRef, mPtr); break; case 0: throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown ID)"); From 3ebfb479830c02ba1172de99341670f314c4d443 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 2 Jan 2016 01:12:44 +0100 Subject: [PATCH 048/765] Do not discard root node transformations if the root node is named 'bip01' (Bug #3118) --- components/nif/node.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 326e9802fd..17b58bd463 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -9,6 +9,8 @@ #include "controller.hpp" #include "base.hpp" +#include + namespace Nif { @@ -118,10 +120,10 @@ struct NiNode : Node children.read(nif); effects.read(nif); - // Discard tranformations for the root node, otherwise some meshes + // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but // can be expanded if needed. - if (0 == recIndex) + if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) { static_cast(this)->trafo = Nif::Transformation::getIdentity(); } From b1020dcd42798fc024a4660e7698ea88840337b7 Mon Sep 17 00:00:00 2001 From: Poncho Date: Sat, 2 Jan 2016 12:24:39 -0600 Subject: [PATCH 049/765] Use correct direction multipliers during awareness check --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b98e5daa36..b8dc90cf7a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1435,7 +1435,7 @@ namespace MWMechanics osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); float angleRadians = std::acos(observerDir * vec / (observerDir.length() * vec.length())); - if (angleRadians < osg::DegreesToRadians(90.f)) + if (angleRadians > osg::DegreesToRadians(90.f)) y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; else y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; From ded0962130e14fb16a358a7ac2013601d6c59417 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 2 Jan 2016 20:52:22 +0100 Subject: [PATCH 050/765] Update AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 292c1e9e3d..d4927ea92f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -56,6 +56,7 @@ Programmers Jeffrey Haines (Jyby) Jengerer Jiří Kuneš (kunesj) + Joe Wilkerson (neuralroberts) Joel Graff (graffy) John Blomberg (fstp) Jordan Ayers From e695619aa5bfe5d8a80580fbd322fac653f0048b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Jan 2016 00:02:58 +0100 Subject: [PATCH 051/765] ExprParser: Warn about ignored arguments --- components/compiler/exprparser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index c0375b4366..d35b081677 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -804,6 +804,8 @@ namespace Compiler if (optional) ++optionalCount; } + else + getErrorHandler().warning("Ignoring extra argument", mTokenLoc); } else if (*iter=='X') { @@ -815,6 +817,8 @@ namespace Compiler if (parser.isEmpty()) break; + else + getErrorHandler().warning("Ignoring extra argument", mTokenLoc); } else if (*iter=='z') { @@ -825,6 +829,8 @@ namespace Compiler if (discardParser.isEmpty()) break; + else + getErrorHandler().warning("Ignoring extra argument", mTokenLoc); } else if (*iter=='j') { From 23cd2056bf838a07bb67ffdb9eda5081c9ae9c47 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Jan 2016 00:03:41 +0100 Subject: [PATCH 052/765] Ignore extra arguments in playSound and playSound3D. A common mistake in mods is to use playSound with volume/pitch arguments, which only playSoundVP supports. Previously these extra arguments raised a parser error, making the respective mod unusable. --- components/compiler/extensions0.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 8f7650191a..8c76cdbb80 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -341,9 +341,9 @@ namespace Compiler extensions.registerInstruction ("say", "SS", opcodeSay, opcodeSayExplicit); extensions.registerFunction ("saydone", 'l', "", opcodeSayDone, opcodeSayDoneExplicit); extensions.registerInstruction ("streammusic", "S", opcodeStreamMusic); - extensions.registerInstruction ("playsound", "c", opcodePlaySound); + extensions.registerInstruction ("playsound", "cXX", opcodePlaySound); extensions.registerInstruction ("playsoundvp", "cff", opcodePlaySoundVP); - extensions.registerInstruction ("playsound3d", "c", opcodePlaySound3D, + extensions.registerInstruction ("playsound3d", "cXX", opcodePlaySound3D, opcodePlaySound3DExplicit); extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP, opcodePlaySound3DVPExplicit); From ea0be6e737d29b60a987c301f1cfb66174b881ad Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Jan 2016 00:08:05 +0100 Subject: [PATCH 053/765] Update ScriptArgs documentation --- components/compiler/extensions.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 9fb9bdb95a..2adf25a57d 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -20,9 +20,9 @@ namespace Compiler l - Integer
s - Short
S - String, case preserved
- x - Optional, ignored string argument - X - Optional, ignored numeric expression - z - Optional, ignored string or numeric argument + x - Optional, ignored string argument. Emits a parser warning when this argument is supplied.
+ X - Optional, ignored numeric expression. Emits a parser warning when this argument is supplied.
+ z - Optional, ignored string or numeric argument. Emits a parser warning when this argument is supplied.
j - A piece of junk (either . or a specific keyword) **/ typedef std::string ScriptArgs; From 0597c8fd9c748b0da4c6df76530e16176266d17d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Jan 2016 16:43:20 +0100 Subject: [PATCH 054/765] Pass a Vec4f by reference --- components/sceneutil/lightcontroller.cpp | 2 +- components/sceneutil/lightcontroller.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 511937a282..e3ea93843c 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -125,7 +125,7 @@ namespace SceneUtil traverse(node, nv); } - void LightController::setDiffuse(osg::Vec4f color) + void LightController::setDiffuse(const osg::Vec4f& color) { mDiffuseColor = color; } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index f6e2fa9faf..8f70af343d 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -24,7 +24,7 @@ namespace SceneUtil void setType(LightType type); - void setDiffuse(osg::Vec4f color); + void setDiffuse(const osg::Vec4f& color); virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); From daa94cc50e46038ffde0f93a816ccc5c0ff55af8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Jan 2016 18:20:34 +0100 Subject: [PATCH 055/765] Fix cppcheck warnings --- apps/essimporter/importer.cpp | 2 +- apps/mwiniimporter/importer.cpp | 10 ++++++++-- apps/opencs/model/doc/loader.cpp | 3 +-- apps/opencs/model/tools/magiceffectcheck.cpp | 2 +- apps/opencs/model/tools/search.cpp | 9 +++++---- apps/opencs/model/tools/soundgencheck.cpp | 2 +- apps/opencs/model/world/nestedcoladapterimp.cpp | 6 +++--- apps/opencs/model/world/refidadapterimp.cpp | 6 +++--- apps/opencs/view/world/datadisplaydelegate.cpp | 2 +- apps/opencs/view/world/datadisplaydelegate.hpp | 2 +- apps/opencs/view/world/dialoguesubview.cpp | 2 -- apps/openmw/mwmechanics/aicombat.cpp | 1 - apps/openmw/mwmechanics/aifollow.cpp | 4 +++- apps/openmw/mwphysics/physicssystem.cpp | 3 +++ apps/openmw/mwrender/sky.cpp | 2 +- components/contentselector/model/contentmodel.cpp | 3 +-- components/contentselector/model/loadordererror.hpp | 7 ++----- components/myguiplatform/myguiplatform.hpp | 3 +++ components/nifosg/controller.cpp | 2 +- components/sceneutil/riggeometry.cpp | 2 +- components/sceneutil/workqueue.cpp | 4 ++-- 21 files changed, 42 insertions(+), 35 deletions(-) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 4fbf062170..3d94f2b90d 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -54,7 +54,7 @@ namespace *(image->data(x,y)+2) = *it++; *(image->data(x,y)+1) = *it++; *image->data(x,y) = *it++; - it++; // skip alpha + ++it; // skip alpha } } diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f90ba4184f..f2fcdb711a 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,5 +1,6 @@ #include "importer.hpp" +#include #include #include #include @@ -897,8 +898,13 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename boost::filesystem::path resolved = filename; #endif writeTime = boost::filesystem::last_write_time(resolved); - std::cout << "content file: " << resolved << " timestamp = (" << writeTime << - ") " << asctime(localtime(&writeTime)) << std::endl; + + // print timestamp + const int size=1024; + char timeStrBuffer[size]; + if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0) + std::cout << "content file: " << resolved << " timestamp = (" << writeTime << + ") " << timeStrBuffer << std::endl; } else { diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index cb3ff2cd0f..166e6f3dbf 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -45,13 +45,12 @@ void CSMDoc::Loader::load() bool done = false; - const int batchingSize = 50; - try { if (iter->second.mRecordsLeft) { Messages messages (Message::Severity_Error); + const int batchingSize = 50; for (int i=0; igetData().continueLoading (messages)) { diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index 5435881b34..ab8b3b68bd 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -7,7 +7,7 @@ namespace { - void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string& text) { if (!text.empty()) { diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 0409199afe..0c068ba119 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -120,24 +120,25 @@ QString CSMTools::Search::flatten (const QString& text) const return flat; } -CSMTools::Search::Search() : mType (Type_None), mPaddingBefore (10), mPaddingAfter (10) {} +CSMTools::Search::Search() : mType (Type_None), mValue (0), mIdColumn (0), mTypeColumn (0), + mPaddingBefore (10), mPaddingAfter (10) {} CSMTools::Search::Search (Type type, const std::string& value) -: mType (type), mText (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mText (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_Text && type!=Type_Id) throw std::logic_error ("Invalid search parameter (string)"); } CSMTools::Search::Search (Type type, const QRegExp& value) -: mType (type), mRegExp (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mRegExp (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_TextRegEx && type!=Type_IdRegEx) throw std::logic_error ("Invalid search parameter (RegExp)"); } CSMTools::Search::Search (Type type, int value) -: mType (type), mValue (value), mPaddingBefore (10), mPaddingAfter (10) +: mType (type), mValue (value), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_RecordState) throw std::logic_error ("invalid search parameter (int)"); diff --git a/apps/opencs/model/tools/soundgencheck.cpp b/apps/opencs/model/tools/soundgencheck.cpp index bdf89f19d2..a36c494a1a 100644 --- a/apps/opencs/model/tools/soundgencheck.cpp +++ b/apps/opencs/model/tools/soundgencheck.cpp @@ -26,7 +26,7 @@ void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages return; } - const ESM::SoundGenerator soundGen = record.get(); + const ESM::SoundGenerator& soundGen = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId); if (!soundGen.mCreature.empty()) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index bfacc15dc9..92b4b9e624 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -277,7 +277,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < rowToRemove; ++i) - iter++; + ++iter; reactions.erase(iter); record.setModified (faction); @@ -314,7 +314,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::const_iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) - iter++; + ++iter; switch (subColIndex) { case 0: return QString((*iter).first.c_str()); @@ -337,7 +337,7 @@ namespace CSMWorld // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) - iter++; + ++iter; std::string factionId = (*iter).first; int reaction = (*iter).second; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 90a710fc89..039624c84b 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1153,7 +1153,7 @@ QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdCol const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); if (subColIndex == 0) return subRowIndex; @@ -1259,7 +1259,7 @@ QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) throw std::runtime_error ("index out of range"); @@ -1337,7 +1337,7 @@ QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *c const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); - const ESM::Creature creature = record.get(); + const ESM::Creature& creature = record.get(); switch (subColIndex) { diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 51d7137ec5..9db16d593e 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -140,7 +140,7 @@ void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *set } -void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename) +void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& enumName, const QString& iconFilename) { mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename))); EnumDelegateFactory::add(enumValue, enumName); diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index cde109fd4c..540216d78a 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -83,7 +83,7 @@ namespace CSVWorld protected: - void add (int enumValue,const QString enumName, const QString iconFilename); + void add (int enumValue, const QString& enumName, const QString& iconFilename); }; diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 25bd8e8ee4..0a79aac2b1 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -674,8 +674,6 @@ void CSVWorld::EditWidget::remake(int row) { mNestedTableMapper->addMapping (editor, col); - std::string disString = tree->nestedHeaderData (i, col, - Qt::Horizontal, Qt::DisplayRole).toString().toStdString(); // Need to use Qt::DisplayRole in order to get the correct string // from CSMWorld::Columns QLabel* label = new QLabel (tree->nestedHeaderData (i, col, diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 3cea00e45b..3aeeedad1f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -305,7 +305,6 @@ namespace MWMechanics else { distantCombat = (rangeAttack > 500); - weapRange = 150.f; } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index d9356da93f..1430d42f20 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -129,12 +129,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); - const float threshold = 10; if (storage.mMoving) //Stop when you get close storage.mMoving = (dist > followDistance); else + { + const float threshold = 10; storage.mMoving = (dist > followDistance + threshold); + } if(!storage.mMoving) { diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f6883ae35d..57bf7919a8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -506,6 +506,9 @@ namespace MWPhysics private: btHeightfieldTerrainShape* mShape; btCollisionObject* mCollisionObject; + + void operator=(const HeightField&); + HeightField(const HeightField&); }; // -------------------------------------------------------------- diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 20e3dc07c7..635116db0a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -366,7 +366,7 @@ public: for (unsigned int i=0; isize(); ++i) { float alpha = 1.f; - if (mMeshType == 0) alpha = i%2 ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row + if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row else if (mMeshType == 1) { if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 8dc4351f6d..26f0a48069 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -110,12 +110,11 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index bool gamefileChecked = (file->gameFiles().count() == 0); foreach (const QString &fileName, file->gameFiles()) { - bool depFound = false; foreach (EsmFile *dependency, mFiles) { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. - depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0); + bool depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0); if (!depFound) continue; diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp index 2b840cf69b..7067f1f22c 100644 --- a/components/contentselector/model/loadordererror.hpp +++ b/components/contentselector/model/loadordererror.hpp @@ -17,12 +17,9 @@ namespace ContentSelectorModel ErrorCode_LoadOrder = 3 }; - inline LoadOrderError() : mErrorCode(ErrorCode_None) {}; + inline LoadOrderError() : mErrorCode(ErrorCode_None) {} inline LoadOrderError(ErrorCode errorCode, QString fileName) - { - mErrorCode = errorCode; - mFileName = fileName; - } + : mErrorCode(errorCode), mFileName(fileName) {} inline ErrorCode errorCode() const { return mErrorCode; } inline QString fileName() const { return mFileName; } QString toolTip() const; diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp index 56562e12a6..90d45ce204 100644 --- a/components/myguiplatform/myguiplatform.hpp +++ b/components/myguiplatform/myguiplatform.hpp @@ -47,6 +47,9 @@ namespace osgMyGUI DataManager* mDataManager; MyGUI::LogManager* mLogManager; LogFacility* mLogFacility; + + void operator=(const Platform&); + Platform(const Platform&); }; } diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 28f61e4b6c..d0abc9ead2 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -258,7 +258,7 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) mat.setTrans(uTrans, vTrans, 0); // setting once is enough because all other texture units share the same TexMat (see setDefaults). - if (mTextureUnits.size()) + if (!mTextureUnits.empty()) { osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(*mTextureUnits.begin(), osg::StateAttribute::TEXMAT)); texMat->setMatrix(mat); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 88b907fafa..be8d97a4cb 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -177,7 +177,7 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) } } - for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); it++) + for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it) { mBone2VertexMap[it->second].push_back(it->first); } diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index b642687f0f..26a392be45 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -59,7 +59,7 @@ WorkQueue::~WorkQueue() { { OpenThreads::ScopedLock lock(mMutex); - while (mQueue.size()) + while (!mQueue.empty()) { WorkItem* item = mQueue.front(); delete item; @@ -88,7 +88,7 @@ osg::ref_ptr WorkQueue::addWorkItem(WorkItem *item) WorkItem *WorkQueue::removeWorkItem() { OpenThreads::ScopedLock lock(mMutex); - while (!mQueue.size() && !mIsReleased) + while (mQueue.empty() && !mIsReleased) { mCondition.wait(&mMutex); } From 6c676c861cad886b04f1da9c129e6b27315b6ea0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 4 Jan 2016 18:03:47 +0100 Subject: [PATCH 056/765] increased version number --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afe3ce4b70..f5422a1677 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 37) +set(OPENMW_VERSION_MINOR 38) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") diff --git a/README.md b/README.md index f353cd76ef..9cfb0440db 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.37.0 +* Version: 0.38.0 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net From fde831e2e03281b1461e317318ebb1be82785e00 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 4 Jan 2016 18:21:30 +0100 Subject: [PATCH 057/765] updated changelog --- CHANGELOG.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e598566c..de4a6fc248 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,77 @@ +0.38.0 +------ + + Bug #1699: Guard will continuously run into mudcrab + Bug #1934: Saw in Dome of Kasia doesnt harm the player + Bug #1962: Rat floats when killed near the door + Bug #1963: Kwama eggsacks pulse too fast + Bug #2198: NPC voice sound source should be placed at their head + Bug #2210: OpenMW installation wizard crashes... + Bug #2211: Editor: handle DELE subrecord at the end of a record + Bug #2413: ESM error Unknown subrecord in Grandmaster of Hlaalu + Bug #2537: Bloodmoon quest Ristaag: Sattir not consistently dying, plot fails to advance; same with Grerid + Bug #2697: "The Swimmer" moves away after leading you to underwater cave + Bug #2724: Loading previous save duplicates containers and harvestables + Bug #2769: Inventory doll - Cursor not respecting order of clothes + Bug #2865: Scripts silently fail when moving NPCs between cells. + Bug #2873: Starting a new game leads to CTD / Fatal Error + Bug #2918: Editor: it's not possible to create an omwaddon containing a dot in the file name + Bug #2933: Dialog box can't disable a npc if it is in another cell. (Rescue Madura Seran). + Bug #2942: atronach sign behavior (spell absorption) changes when trying to receive a blessing at "shrine of tribunal" + Bug #2952: Enchantment Merchant Items reshuffled EVERY time 'barter' is clicked + Bug #2961: ESM Error: Unknown subrecord if Deus Ex Machina mod is loaded + Bug #2972: Resurrecting the player via console does not work when health was 0 + Bug #2986: Projectile weapons work underwater + Bug #2988: "Expected subrecord" bugs showing up. + Bug #2991: Can't use keywords in strings for MessageBox + Bug #2993: Tribunal:The Shrine of the Dead – Urvel Dulni can't stop to follow the player. + Bug #3008: NIFFile Error while loading meshes with a NiLODNode + Bug #3010: Engine: items should sink to the ground when dropped under water + Bug #3011: NIFFile Error while loading meshes with a NiPointLight + Bug #3016: Engine: something wrong with scripting - crash / fatal error + Bug #3020: Editor: verify does not check if given "item ID" (as content) for a "container" exists + Bug #3026: [MOD: Julan Ashlander Companion] Dialogue not triggering correctly + Bug #3028: Tooltips for Health, Magicka and Fatigue show in Options menu even when bars aren't visible + Bug #3034: Item count check dialogue option doesn't work (Guards accept gold even if you don't have enough) + Bug #3036: Owned tooltip color affects spell tooltips incorrrectly + Bug #3037: Fatal error loading old ES_Landscape.esp in Store::search + Bug #3038: Player sounds come from underneath + Bug #3040: Execution of script failed: There is a message box already + Bug #3047: [MOD: Julan Ashlander Companion] Scripts KS_Bedscript or KS_JulanNight not working as intended + Bug #3048: Fatal Error + Bug #3051: High field of view results in first person rendering glitches + Bug #3053: Crash on new game at character class selection + Bug #3058: Physiched sleeves aren't rendered correctly. + Bug #3060: NPCs use wrong landing sound + Bug #3062: Mod support regression: Andromeda's fast travel. + Bug #3063: Missing Journal Textures without Tribunal and Bloodmoon installed + Bug #3077: repeated aifollow causes the distance to stack + Bug #3078: Creature Dialogues not showing when certain Function/Conditions are required. + Bug #3082: Crash when entering Holamayan Monastery with mesh replacer installed + Bug #3086: Party at Boro's House – Creature with Class don't talk under OpenMW + Bug #3089: Dreamers spawn too soon + Bug #3100: Certain controls erroneously work as a werewolf + Bug #3102: Multiple unique soultrap spell sources clone souls. + Bug #3105: Summoned creatures and objects disappear at midnight + Bug #3112: gamecontrollerdb file creation with wrong extension + Bug #3116: Dialogue Function "Same Race" is avoided + Bug #3117: Dialogue Bug: Choice conditions are tested when not in a choice + Bug #3118: Body Parts are not rendered when used in a pose. + Bug #3122: NPC direction is reversed during sneak awareness check + Feature #776: Sound effects from one direction don't necessarily affect both speakers in stereo + Feature #858: Different fov settings for hands and the game world + Feature #1176: Handle movement of objects between cells + Feature #2507: Editor: choosing colors for syntax highlighting + Feature #2867: Editor: hide script error list when there are no errors + Feature #2885: Accept a file format other than nif + Feature #2982: player->SetDelete 1 results in: PC can't move, menu can be opened + Feature #2996: Editor: make it possible to preset the height of the script check area in a script view + Feature #3014: Editor: Tooltips in 3D scene + Feature #3064: Werewolf field of view + Feature #3074: Quicksave indicator + Task #287: const version of Ptr + Task #2542: Editor: redo user settings system + 0.37.0 ------ From e6619c5306a5305a5aebb2bc5a17db5538162391 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Jan 2016 20:27:38 +0100 Subject: [PATCH 058/765] Rebuild actor animations on resurrection (Fixes #3124) A new animation is necessary to set up the correct InventoryStore listener, to get notified of changes like the actor no longer being a werewolf, etc. --- apps/openmw/mwscript/statsextensions.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index cdf60d3298..d5b64b7d73 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1125,10 +1125,16 @@ namespace MWScript ptr.getClass().getCreatureStats(ptr).resurrect(); else if (ptr.getClass().getCreatureStats(ptr).isDead()) { + bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); - // resets runtime state such as inventory, stats and AI. does not reset position in the world MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); + + // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). + MWBase::Environment::get().getWorld()->disable(ptr); + // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(NULL); + if (wasEnabled) + MWBase::Environment::get().getWorld()->enable(ptr); } } }; From 446c7147270ee69148a82d431309fdd3f8b458ce Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Jan 2016 20:39:08 +0100 Subject: [PATCH 059/765] Fix a possible memory leak in error case --- apps/openmw/mwrender/npcanimation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4e367b3b18..a032896a71 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -352,13 +352,13 @@ public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); - osg::RefMatrix* projectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); float fov, aspect, zNear, zFar; - if (projectionMatrix->getPerspective(fov, aspect, zNear, zFar)) + if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) { fov = mFov; - projectionMatrix->makePerspective(fov, aspect, zNear, zFar); - cv->pushProjectionMatrix(projectionMatrix); + osg::RefMatrix* newProjectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); + cv->pushProjectionMatrix(newProjectionMatrix); traverse(node, nv); cv->popProjectionMatrix(); } From 48f4fc34eba0386d53f19507ed423cfeeb6c6e8f Mon Sep 17 00:00:00 2001 From: sandstranger Date: Tue, 5 Jan 2016 15:37:17 +0300 Subject: [PATCH 060/765] build fixes for Android --- CMakeLists.txt | 6 +++++- cmake/FindMyGUI.cmake | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afe3ce4b70..fd7e3d564d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,11 @@ endif (APPLE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") + set (OSG_PLUGINS_DIR CACHE STRING "") +endif() + # Version message(STATUS "Configuring OpenMW...") @@ -157,7 +162,6 @@ if (WIN32) endif() if (ANDROID) - set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) endif (ANDROID) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 6e93a92ce6..61a39e4d3a 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -71,7 +71,7 @@ ELSE (WIN32) #Unix FIND_PACKAGE(PkgConfig) IF(MYGUI_STATIC) # don't use pkgconfig on OS X, find freetype & append it's libs to resulting MYGUI_LIBRARIES - IF (NOT APPLE) + IF (NOT APPLE AND NOT ANDROID) PKG_SEARCH_MODULE(MYGUI MYGUIStatic MyGUIStatic) IF (MYGUI_INCLUDE_DIRS) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) @@ -84,15 +84,15 @@ ELSE (WIN32) #Unix STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") ENDIF (MYGUI_INCLUDE_DIRS) - ELSE (NOT APPLE) + ELSE (NOT APPLE AND NOT ANDROID) SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR}) - FIND_PACKAGE(freetype) + FIND_PACKAGE(Freetype REQUIRED) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib) + FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib ${OPENMW_DEPENDENCIES_DIR}) SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") - ENDIF (NOT APPLE) + ENDIF (NOT APPLE AND NOT ANDROID) ELSE(MYGUI_STATIC) PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI) IF (MYGUI_INCLUDE_DIRS) @@ -109,6 +109,7 @@ ELSE (WIN32) #Unix ENDIF(MYGUI_STATIC) ENDIF (WIN32) + #Do some preparation IF (NOT WIN32) # This does not work on Windows for paths with spaces in them SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) From 5952498e9e747dc8d7dc1a62866abbace2698cab Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Jan 2016 16:53:51 +0100 Subject: [PATCH 061/765] Explicitely cast a size_t to int --- apps/openmw/mwgui/dialogue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d325886316..040cd6d651 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -547,7 +547,7 @@ namespace MWGui void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) { - mHistory->setPosition(0, pos * -1); + mHistory->setPosition(0, static_cast(pos) * -1); } void DialogueWindow::addResponse(const std::string &text, const std::string &title) From d9d6228c94ac961fc98b4e214efe30f0882d79db Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Jan 2016 16:54:09 +0100 Subject: [PATCH 062/765] Implement PageDisplay::_updateView (Fixes #3125) --- apps/openmw/mwgui/bookpage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index b728b748f7..fe1e6aa0c1 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1222,6 +1222,11 @@ public: void _updateView () { + _checkMargin(); + + if (mNode != NULL) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + mNode->outOfDate (i->second->mRenderItem); } void _correctView() From 745b29a99509197b0c0842403697f366b2a8b439 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Jan 2016 17:01:21 +0100 Subject: [PATCH 063/765] Remove a debugging leftover --- apps/openmw/mwgui/bookpage.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index fe1e6aa0c1..8506f04edf 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1153,8 +1153,6 @@ public: void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) { - //test (); - mNode = node; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) From 466f91db673b6c58864228b49ae62224a3ab3381 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 6 Jan 2016 13:10:09 +0100 Subject: [PATCH 064/765] Fixed wrong GMST name for newly created game files (Fixes #3132) --- apps/opencs/model/doc/document.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 0e2d4e7d1a..8cafa7ae1f 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1481,7 +1481,7 @@ void CSMDoc::Document::addGmsts() "sMagicWingedTwilightID", "sMagnitude", "sMagnitudeDes", - "sMake", + "sMake Enchantment", "sMap", "sMaster", "sMastPlugMismatchMsg", From 210c77968a6542b401a8784c424b1b5935f631af Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 7 Jan 2016 15:38:23 +0100 Subject: [PATCH 065/765] Don't report warning about extra arguments, if there are no extra arguments (Fixes #3133) --- components/compiler/exprparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index d35b081677..c739048a61 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -791,7 +791,7 @@ namespace Compiler if (*iter=='c') stringParser.smashCase(); scanner.scan (stringParser); - if (optional && stringParser.isEmpty()) + if ((optional || *iter=='x') && stringParser.isEmpty()) break; if (*iter!='x') From f9607a47b319afcb4f02c51bf0ae3c57104b3119 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 7 Jan 2016 15:54:22 +0100 Subject: [PATCH 066/765] improved handling of extra arguments in StringParser --- components/compiler/exprparser.cpp | 4 +++- components/compiler/stringparser.cpp | 27 ++++++++++++++++++++++----- components/compiler/stringparser.hpp | 11 +++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index c739048a61..37c20527ef 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -789,6 +789,7 @@ namespace Compiler stringParser.setOptional (true); if (*iter=='c') stringParser.smashCase(); + if (*iter=='x') stringParser.discard(); scanner.scan (stringParser); if ((optional || *iter=='x') && stringParser.isEmpty()) @@ -805,7 +806,8 @@ namespace Compiler ++optionalCount; } else - getErrorHandler().warning("Ignoring extra argument", mTokenLoc); + getErrorHandler().warning ("Ignoring extra argument", + stringParser.getTokenLoc()); } else if (*iter=='X') { diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index f8798eccd4..8041b0f024 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -13,7 +13,7 @@ namespace Compiler { StringParser::StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals) - : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false) + : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false), mDiscard (false) { } @@ -24,10 +24,15 @@ namespace Compiler if (mState==StartState || mState==CommaState) { start(); - if (mSmashCase) - Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); - else - Generator::pushString (mCode, mLiterals, name); + mTokenLoc = loc; + + if (!mDiscard) + { + if (mSmashCase) + Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); + else + Generator::pushString (mCode, mLiterals, name); + } return false; } @@ -75,6 +80,8 @@ namespace Compiler mState = StartState; mCode.clear(); mSmashCase = false; + mTokenLoc = TokenLoc(); + mDiscard = false; Parser::reset(); } @@ -82,4 +89,14 @@ namespace Compiler { mSmashCase = true; } + + const TokenLoc& StringParser::getTokenLoc() const + { + return mTokenLoc; + } + + void StringParser::discard() + { + mDiscard = true; + } } diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 52469128fd..72dab05801 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -6,6 +6,7 @@ #include #include "parser.hpp" +#include "tokenloc.hpp" namespace Compiler { @@ -22,6 +23,8 @@ namespace Compiler State mState; std::vector mCode; bool mSmashCase; + TokenLoc mTokenLoc; + bool mDiscard; public: @@ -48,6 +51,14 @@ namespace Compiler void reset(); ///< Reset parser to clean state (this includes the smashCase function). + + /// Returns TokenLoc object for string. If no string has been parsed, the TokenLoc + /// object will be default initialised. + const TokenLoc& getTokenLoc() const; + + /// If parsing a string, do not add it to the literal table and do not create code + /// for it. + void discard(); }; } From 46e32de3501d170887d8d91bda62030e1f89d058 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 7 Jan 2016 16:01:01 +0100 Subject: [PATCH 067/765] improved handling of extra arguments in DiscardParser --- components/compiler/discardparser.cpp | 25 +++++++++++++++++++++++++ components/compiler/discardparser.hpp | 7 ++++++- components/compiler/exprparser.cpp | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index da114fb3dd..0e7c4718cb 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -14,6 +14,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState || mState==MinusState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -25,6 +28,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState || mState==MinusState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -37,6 +43,9 @@ namespace Compiler { if (mState==StartState || mState==CommaState) { + if (isEmpty()) + mTokenLoc = loc; + start(); return false; } @@ -48,12 +57,22 @@ namespace Compiler { if (code==Scanner::S_comma && mState==StartState) { + if (isEmpty()) + mTokenLoc = loc; + + start(); + mState = CommaState; return true; } if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) { + if (isEmpty()) + mTokenLoc = loc; + + start(); + mState = MinusState; return true; } @@ -64,6 +83,12 @@ namespace Compiler void DiscardParser::reset() { mState = StartState; + mTokenLoc = TokenLoc(); Parser::reset(); } + + const TokenLoc& DiscardParser::getTokenLoc() const + { + return mTokenLoc; + } } diff --git a/components/compiler/discardparser.hpp b/components/compiler/discardparser.hpp index bee8a87bb2..2a7ed5544d 100644 --- a/components/compiler/discardparser.hpp +++ b/components/compiler/discardparser.hpp @@ -2,6 +2,7 @@ #define COMPILER_DISCARDPARSER_H_INCLUDED #include "parser.hpp" +#include "tokenloc.hpp" namespace Compiler { @@ -14,6 +15,7 @@ namespace Compiler }; State mState; + TokenLoc mTokenLoc; public: @@ -38,8 +40,11 @@ namespace Compiler virtual void reset(); ///< Reset parser to clean state. + + /// Returns TokenLoc object for value. If no value has been parsed, the TokenLoc + /// object will be default initialised. + const TokenLoc& getTokenLoc() const; }; } #endif - diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 37c20527ef..7c61ad0ee5 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -832,7 +832,7 @@ namespace Compiler if (discardParser.isEmpty()) break; else - getErrorHandler().warning("Ignoring extra argument", mTokenLoc); + getErrorHandler().warning("Ignoring extra argument", discardParser.getTokenLoc()); } else if (*iter=='j') { From ece40b1e96661c21a277d42349fd6b0cd28c5d86 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 7 Jan 2016 16:07:49 +0100 Subject: [PATCH 068/765] improved the remaining handling of extra arguments --- components/compiler/exprparser.cpp | 10 ++++++++-- components/compiler/exprparser.hpp | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 7c61ad0ee5..0c8220e974 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -639,7 +639,8 @@ namespace Compiler if (code==Scanner::S_newline) { // end marker - mTokenLoc = loc; + if (mTokenLoc.mLiteral.empty()) + mTokenLoc = loc; scanner.putbackSpecial (code, loc); return false; } @@ -820,7 +821,7 @@ namespace Compiler if (parser.isEmpty()) break; else - getErrorHandler().warning("Ignoring extra argument", mTokenLoc); + getErrorHandler().warning("Ignoring extra argument", parser.getTokenLoc()); } else if (*iter=='z') { @@ -878,4 +879,9 @@ namespace Compiler return optionalCount; } + + const TokenLoc& ExprParser::getTokenLoc() const + { + return mTokenLoc; + } } diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index 639ca65aab..dd8259ee17 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -103,6 +103,8 @@ namespace Compiler /// \param invert Store arguments in reverted order. /// \param ignoreKeyword A keyword that is seen as junk /// \return number of optional arguments + + const TokenLoc& getTokenLoc() const; }; } From 7dd7be7f0ec646a8691877ad0e51defd103f00a5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 10 Jan 2016 08:56:15 +0100 Subject: [PATCH 069/765] make scenes drop target for referenceables --- apps/opencs/view/render/instancemode.cpp | 84 +++++++++++++++++++ apps/opencs/view/render/instancemode.hpp | 4 + .../view/render/pagedworldspacewidget.cpp | 11 +++ .../view/render/pagedworldspacewidget.hpp | 2 + .../view/render/unpagedworldspacewidget.cpp | 5 ++ .../view/render/unpagedworldspacewidget.hpp | 2 + apps/opencs/view/render/worldspacewidget.cpp | 48 +++++++++++ apps/opencs/view/render/worldspacewidget.hpp | 13 +++ 8 files changed, 169 insertions(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index a4d147bb4d..6c3593369c 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -1,8 +1,13 @@ #include "instancemode.hpp" +#include + #include "../../model/prefs/state.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" + #include "elements.hpp" #include "object.hpp" #include "worldspacewidget.hpp" @@ -55,3 +60,82 @@ void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) } } } + +void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) +{ + if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + { + if (!mime->fromDocument (getWorldspaceWidget().getDocument())) + return; + + /// \todo document check + if (mime->holdsType (CSMWorld::UniversalId::Type_Referenceable)) + event->accept(); + } +} + +void CSVRender::InstanceMode::dropEvent (QDropEvent* event) +{ + if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + { + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + + if (!mime->fromDocument (document)) + return; + + osg::Vec3f insertPoint = getWorldspaceWidget().getIntersectionPoint (event->pos()); + + std::string cellId = getWorldspaceWidget().getCellId (insertPoint); + + bool dropped = false; + + std::vector ids = mime->getData(); + + CSMWorld::IdTable& referencesTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + + CSMWorld::IdTable& cellTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + for (std::vector::const_iterator iter (ids.begin()); + iter!=ids.end(); ++iter) + if (mime->isReferencable (iter->getType())) + { + // create reference + std::auto_ptr createCommand ( + new CSMWorld::CreateCommand ( + referencesTable, document.getData().getReferences().getNewId())); + + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionXPos), insertPoint.x()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionYPos), insertPoint.y()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_PositionZPos), insertPoint.z()); + createCommand->addValue (referencesTable.findColumnIndex ( + CSMWorld::Columns::ColumnId_ReferenceableId), + QString::fromUtf8 (iter->getId().c_str())); + + // increase reference count in cell + QModelIndex countIndex = cellTable.getModelIndex (cellId, + cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter)); + + int count = cellTable.data (countIndex).toInt(); + + std::auto_ptr incrementCommand ( + new CSMWorld::ModifyCommand (cellTable, countIndex, count+1)); + + document.getUndoStack().beginMacro (createCommand->text()); + document.getUndoStack().push (createCommand.release()); + document.getUndoStack().push (incrementCommand.release()); + document.getUndoStack().endMacro(); + + dropped = true; + } + + if (dropped) + event->accept(); + } +} diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 66451bd996..7649c241ca 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -20,6 +20,10 @@ namespace CSVRender virtual void primarySelectPressed (osg::ref_ptr tag); virtual void secondarySelectPressed (osg::ref_ptr tag); + + virtual void dragEnterEvent (QDragEnterEvent *event); + + virtual void dropEvent (QDropEvent* event); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dae5990e6f..e30f238a29 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -504,6 +504,17 @@ void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +{ + const int cellSize = 8192; + + CSMWorld::CellCoordinates cellCoordinates ( + static_cast (std::floor (point.x()/cellSize)), + static_cast (std::floor (point.y()/cellSize))); + + return cellCoordinates.getId (mWorldspace); +} + CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index e983fddd55..bc8e0da646 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -96,6 +96,8 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 68f068daca..701f508253 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -108,6 +108,11 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +{ + return mCellId; +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 9e6fa97f43..70a20c216d 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -46,6 +46,8 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index f0d3986414..0383b0bcfa 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -315,6 +315,54 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() return mDocument; } +osg::Vec3f CSVRender::WorldspaceWidget::getIntersectionPoint (const QPoint& localPos, + unsigned int interactionMask, bool ignoreHidden) const +{ + // (0,0) is considered the lower left corner of an OpenGL window + int x = localPos.x(); + int y = height() - localPos.y(); + + osg::ref_ptr intersector ( + new osgUtil::LineSegmentIntersector (osgUtil::Intersector::WINDOW, x, y)); + + intersector->setIntersectionLimit (osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor (intersector); + + unsigned int mask = interactionMask; + + if (ignoreHidden) + mask &= getVisibilityMask(); + + visitor.setTraversalMask (mask << 1); + + mView->getCamera()->accept (visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersector->getIntersections().begin(); + iter!=intersector->getIntersections().end(); ++iter) + { + // reject back-facing polygons + osg::Vec3f normal = osg::Matrix::transform3x3 ( + iter->getWorldIntersectNormal(), mView->getCamera()->getViewMatrix()); + + if (normal.z()>=0) + return iter->getWorldIntersectPoint(); + } + + osg::Matrixd matrix; + matrix.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); + matrix.preMult (mView->getCamera()->getProjectionMatrix()); + matrix.preMult (mView->getCamera()->getViewMatrix()); + matrix = osg::Matrixd::inverse (matrix); + + osg::Vec3d start = matrix.preMult (intersector->getStart()); + osg::Vec3d end = matrix.preMult (intersector->getEnd()); + + osg::Vec3d direction = end-start; + direction.normalize(); + + return start+direction * 50; +} + void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 54376cee45..0edf9f0b29 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -127,6 +127,19 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; + /// Return the next intersection point with scene elements matched by + /// \a interactionMask based on \a localPos and the camera vector. + /// If there is no such point, instead a point "in front" of \a localPos will be + /// returned. + /// + /// \param ignoreHidden ignore elements specified in interactionMask that are + /// flagged as not visible. + osg::Vec3f getIntersectionPoint (const QPoint& localPos, + unsigned int interactionMask = Element_Reference | Element_Terrain, + bool ignoreHidden = false) const; + + virtual std::string getCellId (const osg::Vec3f& point) const = 0; + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); From 5b9d6cce989720aa65828bcba8e13b78d10758af Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 11 Jan 2016 09:03:02 +0100 Subject: [PATCH 070/765] made drop distance configurable --- apps/opencs/model/prefs/state.cpp | 5 +++++ apps/opencs/view/render/worldspacewidget.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 8b827d0a2b..d2ad0f6ef2 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -190,6 +190,11 @@ void CSMPrefs::State::declare() declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). setMin (1); + + declareCategory ("Scene Drops"); + 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"); } void CSMPrefs::State::declareCategory (const std::string& key) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 0383b0bcfa..5361266897 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -360,7 +360,7 @@ osg::Vec3f CSVRender::WorldspaceWidget::getIntersectionPoint (const QPoint& loca osg::Vec3d direction = end-start; direction.normalize(); - return start+direction * 50; + return start + direction * CSMPrefs::get()["Scene Drops"]["distance"].toInt(); } void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) From 190bf158878943f7ad358b8caed706f4f2b02fe7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Jan 2016 22:37:36 +0100 Subject: [PATCH 071/765] SettingsWindow: support auto-updating of slider labels through the layout file --- apps/openmw/mwgui/settingswindow.cpp | 53 ++++++++++------------- apps/openmw/mwgui/settingswindow.hpp | 5 +-- files/mygui/openmw_settings_window.layout | 7 ++- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbfbd0e702..86ecd9dfb0 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -130,12 +130,14 @@ namespace MWGui if (type == sliderType) { MyGUI::ScrollBar* scroll = current->castType(); + std::string valueStr; if (getSettingValueType(current) == "Float") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget float min,max; getSettingMinMax(scroll, min, max); float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + valueStr = MyGUI::utility::toString((int)value); value = std::max(min, std::min(value, max)); value = (value-min)/(max-min); @@ -144,15 +146,30 @@ namespace MWGui else { int value = Settings::Manager::getInt(getSettingName(current), getSettingCategory(current)); + valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + updateSliderLabel(scroll, valueStr); } configureWidgets(current); } } + void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar *scroller, const std::string& value) + { + std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); + if (!labelWidgetName.empty()) + { + MyGUI::TextBox* textBox; + getWidget(textBox, labelWidgetName); + std::string labelCaption = scroller->getUserString("SettingLabelCaption"); + boost::algorithm::replace_all(labelCaption, "%s", value); + textBox->setCaptionWithReplacing(labelCaption); + } + } + SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout"), mKeyboardMode(true) @@ -167,17 +184,13 @@ namespace MWGui getWidget(mFullscreenButton, "FullscreenButton"); getWidget(mVSyncButton, "VSyncButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); - getWidget(mFOVSlider, "FOVSlider"); - getWidget(mAnisotropySlider, "AnisotropySlider"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); - getWidget(mAnisotropyLabel, "AnisotropyLabel"); getWidget(mAnisotropyBox, "AnisotropyBox"); getWidget(mShadersButton, "ShadersButton"); getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); getWidget(mShadowsTextureSize, "ShadowsTextureSize"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); - getWidget(mDifficultySlider, "DifficultySlider"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); @@ -238,7 +251,6 @@ namespace MWGui std::string tmip = Settings::Manager::getString("texture mipmap", "General"); mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); - mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(Settings::Manager::getInt("anisotropy", "General")) + ")"); int waterTextureSize = Settings::Manager::getInt ("rtt size", "Water"); if (waterTextureSize >= 512) @@ -255,15 +267,6 @@ namespace MWGui mShadowsEnabledButton->setEnabled(false); } - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "Camera"))) + ")"); - - MyGUI::TextBox* diffText; - getWidget(diffText, "DifficultyText"); - - diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -439,6 +442,7 @@ namespace MWGui { if (getSettingType(scroller) == "Slider") { + std::string valueStr; if (getSettingValueType(scroller) == "Float") { float value = pos / float(scroller->getScrollRange()-1); @@ -447,28 +451,15 @@ namespace MWGui getSettingMinMax(scroller, min, max); value = min + (max-min) * value; Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); - - if (scroller == mFOVSlider) - { - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(value)) + ")"); - } - if (scroller == mDifficultySlider) - { - MyGUI::TextBox* diffText; - getWidget(diffText, "DifficultyText"); - diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(value)) + ")"); - } + valueStr = MyGUI::utility::toString(int(value)); } else { Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); - if (scroller == mAnisotropySlider) - { - mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(pos) + ")"); - } + valueStr = MyGUI::utility::toString(pos); } + updateSliderLabel(scroller, valueStr); + apply(); } } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 99553808b4..4214653095 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -30,11 +30,7 @@ namespace MWGui MyGUI::Button* mFullscreenButton; MyGUI::Button* mVSyncButton; MyGUI::Button* mWindowBorderButton; - MyGUI::ScrollBar* mFOVSlider; - MyGUI::ScrollBar* mDifficultySlider; - MyGUI::ScrollBar* mAnisotropySlider; MyGUI::ComboBox* mTextureFilteringButton; - MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; MyGUI::Button* mShadersButton; @@ -76,6 +72,7 @@ namespace MWGui void apply(); void configureWidgets(MyGUI::Widget* widget); + void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); private: void resetScrollbars(); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 6d2424aa5b..cf7fe1be70 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -61,6 +61,8 @@ + + @@ -274,7 +276,6 @@ - @@ -285,6 +286,8 @@ + + @@ -334,6 +337,8 @@ + + From 4f8f166f69e6ce2ccecc0a2ce3d8dbc9919dc0cb Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Jan 2016 23:27:42 +0100 Subject: [PATCH 072/765] Fix GetPcInJail to work as in the original engine --- apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 8 ++++++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ebce2e1bf8..462d08727c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -545,6 +545,8 @@ namespace MWBase virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; + + virtual bool isPlayerInJail() const = 0; }; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9c63511b27..593fdcca52 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1014,7 +1014,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime &runtime) { - runtime.push (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail)); + runtime.push (MWBase::Environment::get().getWorld()->isPlayerInJail()); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 15826009d6..19d90e2096 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2996,6 +2996,14 @@ namespace MWWorld } } + bool World::isPlayerInJail() const + { + if (mGoToJail) + return true; + + return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail); + } + void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = getStore().get().find(creatureList); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4ed97759ef..5f0507acef 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -645,6 +645,8 @@ namespace MWWorld /// Return the distance between actor's weapon and target's collision box. virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); + + virtual bool isPlayerInJail() const; }; } From 0da6d249fe5748496d605ad19bc1243d96ad6549 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Jan 2016 00:26:09 +0100 Subject: [PATCH 073/765] Fix the window pinning button's borders not accepting mouse clicks --- files/mygui/openmw_windows.skin.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 682d89ebcc..22270bb9f6 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -11,41 +11,49 @@ + + + + + + + + @@ -53,41 +61,49 @@ + + + + + + + + From 4690fd3f222b3363944df53ab15887a7cc2632c6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Jan 2016 00:43:15 +0100 Subject: [PATCH 074/765] Change the local map exploration radius to better match the original engine --- apps/openmw/mwrender/localmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 9d5950a90e..b3d19090fb 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -526,7 +526,7 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient } // explore radius (squared) - const float exploreRadius = (mInterior ? 0.1f : 0.3f) * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 + const float exploreRadius = 0.17f * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 const float sqrExploreRadius = square(exploreRadius); const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) From ca4e859f613c4521cae17aa4ef19190391e5242c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Jan 2016 00:45:12 +0100 Subject: [PATCH 075/765] Remove unused argument --- apps/openmw/mwgui/mapwindow.cpp | 4 +--- apps/openmw/mwgui/mapwindow.hpp | 2 -- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/localmap.hpp | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 0ebb595ddf..bc8f415994 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -269,8 +269,6 @@ namespace MWGui // normalized cell coordinates float nX,nY; - markerPos.interior = mInterior; - if (!mInterior) { int cellX, cellY; @@ -1078,7 +1076,7 @@ namespace MWGui { if (!mLocalMapRender) return true; - return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior); + return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY); } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index a41d5d5270..0ab38f1e3d 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -78,7 +78,6 @@ namespace MWGui { MarkerUserData(MWRender::LocalMap* map) : mLocalMapRender(map) - , interior(false) , cellX(0) , cellY(0) , nX(0.f) @@ -89,7 +88,6 @@ namespace MWGui bool isPositionExplored() const; MWRender::LocalMap* mLocalMapRender; - bool interior; int cellX; int cellY; float nX; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b3d19090fb..200f484b57 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -478,7 +478,7 @@ osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int return pos; } -bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior) +bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) { const MapSegment& segment = mSegments[std::make_pair(x, y)]; if (!segment.mFogOfWarImage) diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 72ee0354ed..59165013d6 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -99,7 +99,7 @@ namespace MWRender /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) */ - bool isPositionExplored (float nX, float nY, int x, int y, bool interior); + bool isPositionExplored (float nX, float nY, int x, int y); osg::Group* getRoot(); From ef20962fc5f9d2ba19faa679cac1c4c02bbc87a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Jan 2016 01:08:30 +0100 Subject: [PATCH 076/765] Disable fog of war rendering on the HUD map by default --- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/mapwindow.cpp | 11 ++++++----- apps/openmw/mwgui/mapwindow.hpp | 5 +++-- files/settings-default.cfg | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index cf0fa3414a..8d6ce6beb4 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -69,7 +69,7 @@ namespace MWGui HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : Layout("openmw_hud.layout") - , LocalMapBase(customMarkers, localMapRender) + , LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map")) , mHealth(NULL) , mMagicka(NULL) , mStamina(NULL) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index bc8f415994..89f4d8cf37 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -156,7 +156,7 @@ namespace MWGui // ------------------------------------------------------ - LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender) + LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) , mCurX(0) , mCurY(0) @@ -165,7 +165,8 @@ namespace MWGui , mCompass(NULL) , mPrefix() , mChanged(true) - , mFogOfWar(true) + , mFogOfWarToggled(true) + , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) @@ -222,9 +223,9 @@ namespace MWGui bool LocalMapBase::toggleFogOfWar() { - mFogOfWar = !mFogOfWar; + mFogOfWarToggled = !mFogOfWarToggled; applyFogOfWar(); - return mFogOfWar; + return mFogOfWarToggled; } void LocalMapBase::applyFogOfWar() @@ -238,7 +239,7 @@ namespace MWGui int y = mCurY + (-1*(my-1)); MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx]; - if (!mFogOfWar) + if (!mFogOfWarToggled || !mFogOfWarEnabled) { fog->setImageTexture(""); continue; diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 0ab38f1e3d..227a9e3f96 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -61,7 +61,7 @@ namespace MWGui class LocalMapBase { public: - LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender); + LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize); @@ -105,7 +105,8 @@ namespace MWGui MyGUI::ImageBox* mCompass; std::string mPrefix; bool mChanged; - bool mFogOfWar; + bool mFogOfWarToggled; + bool mFogOfWarEnabled; int mMapWidgetSize; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 49c9c54191..6a2495e267 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -47,6 +47,10 @@ global map cell size = 18 # cell, 256 is 1/8 cell. See documentation for details. (e.g. 64 to 256). local map hud widget size = 256 +# Enables Fog of War rendering on the HUD map. Default is Off since with default settings +# the map is so small that the fog would not obscure anything, just darken the edges slightly. +local map hud fog of war = false + # Resolution of local map in GUI window in pixels. See documentation # for details which may affect cell load performance. (e.g. 128 to 1024). local map resolution = 256 From f315a4386fabeb6d96648e58a1c239c557d9257a Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Jan 2016 23:07:01 +0100 Subject: [PATCH 077/765] Accept an implicit reference in Journal (Fixes #3135) --- apps/openmw/mwbase/journal.hpp | 3 ++- apps/openmw/mwdialogue/journalentry.cpp | 8 ++++---- apps/openmw/mwdialogue/journalentry.hpp | 4 ++-- apps/openmw/mwdialogue/journalimp.cpp | 4 ++-- apps/openmw/mwdialogue/journalimp.hpp | 3 ++- apps/openmw/mwscript/dialogueextensions.cpp | 9 +++++++-- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 738014ba6e..cd87928903 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -50,8 +50,9 @@ namespace MWBase virtual ~Journal() {} - virtual void addEntry (const std::string& id, int index) = 0; + virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) = 0; ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). virtual void setJournalIndex (const std::string& id, int index) = 0; ///< Set the journal index without adding an entry. diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 2f5f02b01f..9f74d0733d 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -102,8 +102,8 @@ namespace MWDialogue {} StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth) - : JournalEntry (topic, infoId, MWWorld::Ptr()), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) + int day, int month, int dayOfMonth, const MWWorld::Ptr& actor) + : JournalEntry (topic, infoId, actor), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) @@ -119,12 +119,12 @@ namespace MWDialogue entry.mDayOfMonth = mDayOfMonth; } - StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index) + StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor) { int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); - return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth); + return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth, actor); } } diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index 3ae3efcc8d..8711ab53a7 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -64,13 +64,13 @@ namespace MWDialogue StampedJournalEntry(); StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth); + int day, int month, int dayOfMonth, const MWWorld::Ptr& actor); StampedJournalEntry (const ESM::JournalEntry& record); void write (ESM::JournalEntry& entry) const; - static StampedJournalEntry makeFromQuest (const std::string& topic, int index); + static StampedJournalEntry makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index e6ffe22ab2..8ea72e3ba3 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -75,7 +75,7 @@ namespace MWDialogue mTopics.clear(); } - void Journal::addEntry (const std::string& id, int index) + void Journal::addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) { // bail out of we already have heard this... std::string infoId = JournalEntry::idFromIndex (id, index); @@ -83,7 +83,7 @@ namespace MWDialogue if (i->mTopic == id && i->mInfoId == infoId) return; - StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index); + StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); mJournal.push_back (entry); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 7f26a5bb90..c3e9406290 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -29,8 +29,9 @@ namespace MWDialogue virtual void clear(); - virtual void addEntry (const std::string& id, int index); + virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor); ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). virtual void setJournalIndex (const std::string& id, int index); ///< Set the journal index without adding an entry. diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index c305fb81fc..fcb7e8f3b2 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -22,12 +22,17 @@ namespace MWScript { namespace Dialogue { + template class OpJournal : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { + MWWorld::Ptr ptr = R()(runtime, false); // required=false + if (ptr.isEmpty()) + ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + std::string quest = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -37,7 +42,7 @@ namespace MWScript // Invoking Journal with a non-existing index is allowed, and triggers no errors. Seriously? :( try { - MWBase::Environment::get().getJournal()->addEntry (quest, index); + MWBase::Environment::get().getJournal()->addEntry (quest, index, ptr); } catch (...) { @@ -270,7 +275,7 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); + interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); From 6546c05428bb4820a337b079aa728b62adaaab5e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 12:46:06 +0100 Subject: [PATCH 078/765] Move Fallback map to components/ --- apps/openmw/main.cpp | 34 +------------ apps/openmw/mwbase/world.hpp | 8 +++- apps/openmw/mwgui/charactercreation.cpp | 5 +- apps/openmw/mwgui/levelupdialog.cpp | 3 +- apps/openmw/mwrender/animation.cpp | 5 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/renderingmanager.hpp | 6 +-- apps/openmw/mwrender/ripplesimulation.cpp | 7 ++- apps/openmw/mwrender/ripplesimulation.hpp | 6 +-- apps/openmw/mwrender/sky.cpp | 7 ++- apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwrender/water.hpp | 10 ++-- apps/openmw/mwworld/weather.cpp | 10 ++-- apps/openmw/mwworld/weather.hpp | 14 ++++-- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 15 +++--- components/CMakeLists.txt | 4 ++ .../fallback}/fallback.cpp | 14 +++--- .../fallback}/fallback.hpp | 11 +++-- components/fallback/validate.hpp | 48 +++++++++++++++++++ 20 files changed, 127 insertions(+), 91 deletions(-) rename {apps/openmw/mwworld => components/fallback}/fallback.cpp (75%) rename {apps/openmw/mwworld => components/fallback}/fallback.hpp (67%) create mode 100644 components/fallback/validate.hpp diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index c3f0f8688e..a4bf1fe5ba 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -52,39 +53,8 @@ inline boost::filesystem::path lexical_cast 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)); - } - } -} +using namespace Fallback; /** * \brief Parses application command line and calls \ref Cfg::ConfigurationManager diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ebce2e1bf8..801016e068 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -56,7 +56,6 @@ namespace MWMechanics namespace MWWorld { - class Fallback; class CellStore; class Player; class LocalScripts; @@ -67,6 +66,11 @@ namespace MWWorld typedef std::vector > PtrMovementList; } +namespace Fallback +{ + class Map; +} + namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) @@ -119,7 +123,7 @@ namespace MWBase virtual void adjustSky() = 0; - virtual const MWWorld::Fallback *getFallback () const = 0; + virtual const Fallback::Map *getFallback () const = 0; virtual MWWorld::Player& getPlayer() = 0; virtual MWWorld::Ptr getPlayerPtr() = 0; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index d2ce35509d..8c2ec68813 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -1,5 +1,7 @@ #include "charactercreation.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -10,7 +12,6 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/esmstore.hpp" #include "textinput.hpp" @@ -32,7 +33,7 @@ namespace const ESM::Class::Specialization mSpecializations[3]={ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}; // The specialization for each answer Step sGenerateClassSteps(int number) { number++; - const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback(); Step step = {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_Question"), {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerOne"), fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerTwo"), diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c832a78792..4e8d0a02c4 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -4,13 +4,14 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 68a073731d..c2fc82881f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -35,11 +35,12 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -1113,7 +1114,7 @@ namespace MWRender osg::ref_ptr light (new osg::Light); lightSource->setNodeMask(Mask_Lighting); - const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); float radius = esmLight->mData.mRadius; lightSource->setRadius(radius); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ee9d93c0f2..61ce626fde 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -30,8 +30,8 @@ #include #include +#include -#include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" #include "sky.hpp" @@ -126,7 +126,7 @@ namespace MWRender }; RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, - const MWWorld::Fallback* fallback, const std::string& resourcePath) + const Fallback::Map* fallback, const std::string& resourcePath) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 58012078c4..3b583af89c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -37,9 +37,9 @@ namespace Terrain class World; } -namespace MWWorld +namespace Fallback { - class Fallback; + class Map; } namespace MWRender @@ -58,7 +58,7 @@ namespace MWRender { public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, - const MWWorld::Fallback* fallback, const std::string& resourcePath); + const Fallback::Map* fallback, const std::string& resourcePath); ~RenderingManager(); MWRender::Objects& getObjects(); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f232ea475d..f74631c4a2 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -15,19 +15,18 @@ #include #include #include +#include #include "vismask.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/fallback.hpp" - #include "../mwmechanics/actorutil.hpp" namespace { - void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback, osg::Node* node) + void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback, osg::Node* node) { int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); if (rippleFrameCount <= 0) @@ -78,7 +77,7 @@ namespace namespace MWRender { -RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) +RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback) : mParent(parent) { osg::ref_ptr geode (new osg::Geode); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index a4e12f2756..bca81c59b4 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -21,9 +21,9 @@ namespace Resource class ResourceSystem; } -namespace MWWorld +namespace Fallback { - class Fallback; + class Map; } namespace MWRender @@ -40,7 +40,7 @@ namespace MWRender class RippleSimulation { public: - RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); + RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback); ~RippleSimulation(); /// @param dt Time since the last frame diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 635116db0a..efe797336d 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -42,8 +43,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/fallback.hpp" - #include "vismask.hpp" #include "renderbin.hpp" @@ -827,7 +826,7 @@ private: , mTimeOfDayFade(1.f) , mGlareView(1.f) { - const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); mColor = fallback->getFallbackColour("Weather_Sun_Glare_Fader_Color"); mSunGlareFaderMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Max"); mSunGlareFaderAngleMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Angle_Max"); @@ -1161,7 +1160,7 @@ void SkyManager::create() mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getTextureManager())); - const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); + const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback(); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index cd1f4c5113..1b19246f5b 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -34,8 +34,9 @@ #include +#include + #include "../mwworld/cellstore.hpp" -#include "../mwworld/fallback.hpp" #include "vismask.hpp" #include "ripplesimulation.hpp" @@ -457,7 +458,7 @@ public: }; Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, - const MWWorld::Fallback* fallback, const std::string& resourcePath) + const Fallback::Map* fallback, const std::string& resourcePath) : mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index b26782873f..b02ee392c5 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -28,11 +28,15 @@ namespace Resource namespace MWWorld { - class Fallback; class CellStore; class Ptr; } +namespace Fallback +{ + class Map; +} + namespace MWRender { @@ -50,7 +54,7 @@ namespace MWRender osg::ref_ptr mWaterNode; osg::ref_ptr mWaterGeode; Resource::ResourceSystem* mResourceSystem; - const MWWorld::Fallback* mFallback; + const Fallback::Map* mFallback; osg::ref_ptr mIncrementalCompileOperation; std::auto_ptr mSimulation; @@ -77,7 +81,7 @@ namespace MWRender public: Water(osg::Group* parent, osg::Group* sceneRoot, - Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback, + Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const Fallback::Map* fallback, const std::string& resourcePath); ~Water(); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5ce3d5c2fe..c40913ff1b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -20,7 +21,6 @@ #include "player.hpp" #include "esmstore.hpp" -#include "fallback.hpp" #include "cellstore.hpp" #include @@ -100,7 +100,7 @@ template class TimeOfDayInterpolator; template class TimeOfDayInterpolator; Weather::Weather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, const std::string& particleEffect) @@ -327,7 +327,7 @@ void RegionWeather::chooseNewWeather() mWeather = i; } -MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& fallback) +MoonModel::MoonModel(const std::string& name, const Fallback::Map& fallback) : mFadeInStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Start")) , mFadeInFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Finish")) , mFadeOutStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Start")) @@ -496,7 +496,7 @@ inline float MoonModel::earlyMoonShadowAlpha(float angle) const return 0.0f; } -WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWorld::Fallback& fallback, MWWorld::ESMStore& store) +WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fallback::Map& fallback, MWWorld::ESMStore& store) : mStore(store) , mRendering(rendering) , mSunriseTime(fallback.getFallbackFloat("Weather_Sunrise_Time")) @@ -862,7 +862,7 @@ void WeatherManager::clear() } inline void WeatherManager::addWeather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, const std::string& particleEffect) { static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a5627a507c..0442007570 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -29,9 +29,13 @@ namespace Loading class Listener; } +namespace Fallback +{ + class Map; +} + namespace MWWorld { - class Fallback; class TimeStamp; @@ -66,7 +70,7 @@ namespace MWWorld { public: Weather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, float stormWindSpeed, float rainSpeed, const std::string& particleEffect); @@ -172,7 +176,7 @@ namespace MWWorld class MoonModel { public: - MoonModel(const std::string& name, const MWWorld::Fallback& fallback); + MoonModel(const std::string& name, const Fallback::Map& fallback); MWRender::MoonState calculateState(const TimeStamp& gameTime) const; @@ -203,7 +207,7 @@ namespace MWWorld public: // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time WeatherManager(MWRender::RenderingManager& rendering, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, MWWorld::ESMStore& store); ~WeatherManager(); @@ -288,7 +292,7 @@ namespace MWWorld std::string mPlayingSoundID; void addWeather(const std::string& name, - const MWWorld::Fallback& fallback, + const Fallback::Map& fallback, const std::string& particleEffect = ""); void importRegions(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 15826009d6..789b1b64fa 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -511,7 +511,7 @@ namespace MWWorld return 0; } - const MWWorld::Fallback *World::getFallback() const + const Fallback::Map *World::getFallback() const { return &mFallback; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4ed97759ef..53d640871e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -5,21 +5,20 @@ #include +#include +#include + +#include "../mwbase/world.hpp" + #include "ptr.hpp" #include "scene.hpp" #include "esmstore.hpp" #include "cells.hpp" #include "localscripts.hpp" #include "timestamp.hpp" -#include "fallback.hpp" #include "globals.hpp" - -#include "../mwbase/world.hpp" - #include "contentloader.hpp" -#include - namespace osg { class Group; @@ -71,7 +70,7 @@ namespace MWWorld { Resource::ResourceSystem* mResourceSystem; - MWWorld::Fallback mFallback; + Fallback::Map mFallback; MWRender::RenderingManager* mRendering; MWWorld::WeatherManager* mWeatherManager; @@ -210,7 +209,7 @@ namespace MWWorld virtual void adjustSky(); - virtual const Fallback *getFallback() const; + virtual const Fallback::Map *getFallback() const; virtual Player& getPlayer(); virtual MWWorld::Ptr getPlayerPtr(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bbbff234c4..af26fc9989 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -138,6 +138,10 @@ add_component_dir (version version ) +add_component_dir (fallback + fallback validate + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) diff --git a/apps/openmw/mwworld/fallback.cpp b/components/fallback/fallback.cpp similarity index 75% rename from apps/openmw/mwworld/fallback.cpp rename to components/fallback/fallback.cpp index e810f8241b..3545956606 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -2,12 +2,12 @@ #include -namespace MWWorld +namespace Fallback { - Fallback::Fallback(const std::map& fallback):mFallbackMap(fallback) + Map::Map(const std::map& fallback):mFallbackMap(fallback) {} - std::string Fallback::getFallbackString(const std::string& fall) const + std::string Map::getFallbackString(const std::string& fall) const { std::map::const_iterator it; if((it = mFallbackMap.find(fall)) == mFallbackMap.end()) @@ -16,7 +16,7 @@ namespace MWWorld } return it->second; } - float Fallback::getFallbackFloat(const std::string& fall) const + float Map::getFallbackFloat(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -24,7 +24,7 @@ namespace MWWorld else return boost::lexical_cast(fallback); } - int Fallback::getFallbackInt(const std::string& fall) const + int Map::getFallbackInt(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -33,7 +33,7 @@ namespace MWWorld return boost::lexical_cast(fallback); } - bool Fallback::getFallbackBool(const std::string& fall) const + bool Map::getFallbackBool(const std::string& fall) const { std::string fallback=getFallbackString(fall); if(fallback.empty()) @@ -41,7 +41,7 @@ namespace MWWorld else return boost::lexical_cast(fallback); } - osg::Vec4f Fallback::getFallbackColour(const std::string& fall) const + osg::Vec4f Map::getFallbackColour(const std::string& fall) const { std::string sum=getFallbackString(fall); if(sum.empty()) diff --git a/apps/openmw/mwworld/fallback.hpp b/components/fallback/fallback.hpp similarity index 67% rename from apps/openmw/mwworld/fallback.hpp rename to components/fallback/fallback.hpp index af47063ee8..b44483fbc1 100644 --- a/apps/openmw/mwworld/fallback.hpp +++ b/components/fallback/fallback.hpp @@ -1,18 +1,19 @@ -#ifndef GAME_MWWORLD_FALLBACK_H -#define GAME_MWWORLD_FALLBACK_H +#ifndef OPENMW_COMPONENTS_FALLBACK_H +#define OPENMW_COMPONENTS_FALLBACK_H #include #include #include -namespace MWWorld +namespace Fallback { - class Fallback + /// @brief contains settings imported from the Morrowind INI file. + class Map { const std::map mFallbackMap; public: - Fallback(const std::map& fallback); + Map(const std::map& fallback); std::string getFallbackString(const std::string& fall) const; float getFallbackFloat(const std::string& fall) const; int getFallbackInt(const std::string& fall) const; diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp new file mode 100644 index 0000000000..3b6398d6a2 --- /dev/null +++ b/components/fallback/validate.hpp @@ -0,0 +1,48 @@ +#ifndef OPENMW_COMPONENTS_FALLBACK_VALIDATE_H +#define OPENMW_COMPONENTS_FALLBACK_VALIDATE_H + +#include + +// Parses and validates a fallback map from boost program_options. +// Note: for boost to pick up the validate function, you need to pull in the namespace e.g. +// by using namespace Fallback; + +namespace Fallback +{ + + struct FallbackMap { + std::map mMap; + }; + + void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) + { + 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)); + } + } + } + +} + +#endif From 11496b8075afeee21c93d0837f71ccec47c6d08f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 12:58:36 +0100 Subject: [PATCH 079/765] Read fallback settings in OpenCS --- apps/opencs/editor.cpp | 8 ++++++++ apps/opencs/model/doc/document.cpp | 3 ++- apps/opencs/model/doc/document.hpp | 8 +++++++- apps/opencs/model/doc/documentmanager.cpp | 7 ++++++- apps/opencs/model/doc/documentmanager.hpp | 4 ++++ components/fallback/fallback.hpp | 4 +++- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 92f1082b75..54e323956d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include "model/doc/document.hpp" @@ -17,6 +19,8 @@ #include #endif +using namespace Fallback; + CS::Editor::Editor () : mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), @@ -100,6 +104,8 @@ std::pair > CS::Editor::readConfi ("resources", boost::program_options::value()->default_value("resources")) ("fallback-archive", boost::program_options::value >()-> default_value(std::vector(), "fallback-archive")->multitoken()) + ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") + ->multitoken()->composing(), "fallback values") ("script-blacklist", boost::program_options::value >()->default_value(std::vector(), "") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) @@ -114,6 +120,8 @@ std::pair > CS::Editor::readConfi mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + mDocumentManager.setFallbackMap (variables["fallback"].as().mMap); + if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( variables["script-blacklist"].as >()); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 8cafa7ae1f..59d66f36cc 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2247,6 +2247,7 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) : mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), @@ -2255,7 +2256,7 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), - mResDir(resDir), + mResDir(resDir), mFallbackMap(fallback), mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 0e8ae6d454..41640f66d5 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -25,9 +25,13 @@ class QAbstractItemModel; +namespace Fallback +{ + class Map; +} + namespace VFS { - class Manager; } @@ -66,6 +70,7 @@ namespace CSMDoc Saving mSavingOperation; OperationHolder mSaving; boost::filesystem::path mResDir; + const Fallback::Map* mFallbackMap; Blacklist mBlacklist; Runner mRunner; bool mDirty; @@ -101,6 +106,7 @@ namespace CSMDoc Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 407a608cab..cbb255ebb9 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -64,7 +64,7 @@ CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_) { - return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, &mFallbackMap, mEncoding, mResourcesManager, mBlacklistedScripts); } void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) @@ -100,6 +100,11 @@ void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& par mResDir = boost::filesystem::system_complete(parResDir); } +void CSMDoc::DocumentManager::setFallbackMap(const std::map& fallbackMap) +{ + mFallbackMap = Fallback::Map(fallbackMap); +} + void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) { mEncoding = encoding; diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 4f6b2b2c99..ed8e327d7b 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "../world/resourcesmanager.hpp" @@ -67,6 +68,8 @@ namespace CSMDoc void setResourceDir (const boost::filesystem::path& parResDir); + void setFallbackMap (const std::map& fallbackMap); + void setEncoding (ToUTF8::FromType encoding); void setBlacklistedScripts (const std::vector& scriptIds); @@ -78,6 +81,7 @@ namespace CSMDoc private: boost::filesystem::path mResDir; + Fallback::Map mFallbackMap; private slots: diff --git a/components/fallback/fallback.hpp b/components/fallback/fallback.hpp index b44483fbc1..e649365317 100644 --- a/components/fallback/fallback.hpp +++ b/components/fallback/fallback.hpp @@ -11,9 +11,11 @@ namespace Fallback /// @brief contains settings imported from the Morrowind INI file. class Map { - const std::map mFallbackMap; + std::map mFallbackMap; public: Map(const std::map& fallback); + Map() {} + std::string getFallbackString(const std::string& fall) const; float getFallbackFloat(const std::string& fall) const; int getFallbackInt(const std::string& fall) const; From fb849014bd2c413fe8bb9114ab99f5e31ada73b7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 13:09:53 +0100 Subject: [PATCH 080/765] Pass the fallback map to CSMWorld::Data --- apps/opencs/model/doc/document.cpp | 2 +- apps/opencs/model/world/data.cpp | 10 ++++++++-- apps/opencs/model/world/data.hpp | 10 +++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 59d66f36cc..80c14bd985 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2250,7 +2250,7 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) -: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), +: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager, fallback), mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1f98b24757..6eccb74830 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -59,9 +59,10 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec return number; } -CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) +CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) + mResourcesManager (resourcesManager), mFallbackMap(fallback), + mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { int index = 0; @@ -1201,3 +1202,8 @@ const VFS::Manager* CSMWorld::Data::getVFS() const { return mResourcesManager.getVFS(); } + +const Fallback::Map* CSMWorld::Data::getFallbackMap() const +{ + return mFallbackMap; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index c6623279a6..e5b8229c4f 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -58,6 +58,11 @@ namespace VFS class Manager; } +namespace Fallback +{ + class Map; +} + namespace ESM { class ESMReader; @@ -104,6 +109,7 @@ namespace CSMWorld IdCollection mFilters; Collection mMetaData; const ResourcesManager& mResourcesManager; + const Fallback::Map* mFallbackMap; std::vector mModels; std::map mModelIndex; ESM::ESMReader *mReader; @@ -132,12 +138,14 @@ namespace CSMWorld public: - Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); + Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback); virtual ~Data(); const VFS::Manager* getVFS() const; + const Fallback::Map* getFallbackMap() const; + boost::shared_ptr getResourceSystem(); boost::shared_ptr getResourceSystem() const; From 438b30d6f0a1d6726a80df7ef24e49fd5623c85a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 13:19:15 +0100 Subject: [PATCH 081/765] Move configureLight to a separate file --- apps/openmw/mwrender/animation.cpp | 3 ++- components/CMakeLists.txt | 3 ++- components/sceneutil/lightmanager.cpp | 24 -------------------- components/sceneutil/lightmanager.hpp | 4 ---- components/sceneutil/lightutil.cpp | 32 +++++++++++++++++++++++++++ components/sceneutil/lightutil.hpp | 19 ++++++++++++++++ 6 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 components/sceneutil/lightutil.cpp create mode 100644 components/sceneutil/lightutil.hpp diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index c2fc82881f..6300d28cf1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -30,8 +30,9 @@ #include #include #include -#include +#include #include +#include #include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index af26fc9989..089779eda8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -45,7 +45,8 @@ add_component_dir (resource ) add_component_dir (sceneutil - clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller positionattitudetransform + clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller + lightmanager lightutil positionattitudetransform # not used yet #workqueue ) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 1706bb2b1e..f9182c1d2d 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -371,28 +371,4 @@ namespace SceneUtil traverse(node, nv); } - void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, - float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) - { - bool quadratic = useQuadratic && (!outQuadInLin || isExterior); - - float quadraticAttenuation = 0; - float linearAttenuation = 0; - if (quadratic) - { - float r = radius * quadraticRadiusMult; - quadraticAttenuation = quadraticValue / std::pow(r, 2); - } - if (useLinear) - { - float r = radius * linearRadiusMult; - linearAttenuation = linearValue / r; - } - - light->setLinearAttenuation(linearAttenuation); - light->setQuadraticAttenuation(quadraticAttenuation); - light->setConstantAttenuation(0.f); - - } - } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 3e6d3251bb..07b3b4a62e 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -168,10 +168,6 @@ namespace SceneUtil LightManager::LightList mLightList; }; - /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. - void configureLight(osg::Light* light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, float quadraticValue, - float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue); - } #endif diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp new file mode 100644 index 0000000000..711a94ba8c --- /dev/null +++ b/components/sceneutil/lightutil.cpp @@ -0,0 +1,32 @@ +#include "lightutil.hpp" + +#include + +namespace SceneUtil +{ + + void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) + { + bool quadratic = useQuadratic && (!outQuadInLin || isExterior); + + float quadraticAttenuation = 0; + float linearAttenuation = 0; + if (quadratic) + { + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + } + if (useLinear) + { + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + } + + light->setLinearAttenuation(linearAttenuation); + light->setQuadraticAttenuation(quadraticAttenuation); + light->setConstantAttenuation(0.f); + + } + +} diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp new file mode 100644 index 0000000000..09d23d4a21 --- /dev/null +++ b/components/sceneutil/lightutil.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_LIGHTUTIL_H +#define OPENMW_COMPONENTS_LIGHTUTIL_H + +namespace osg +{ + class Light; +} + +namespace SceneUtil +{ + + /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. + void configureLight(osg::Light* light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, + float linearValue); + +} + +#endif From 3089aeccc4f82aebd9d5ae1801b3d9adcf5621eb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 13:37:55 +0100 Subject: [PATCH 082/765] Factor out SceneUtil::addLight --- apps/openmw/mwrender/animation.cpp | 66 +------------------------- components/sceneutil/lightutil.cpp | 75 ++++++++++++++++++++++++++++++ components/sceneutil/lightutil.hpp | 21 +++++++-- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6300d28cf1..549d0eb8e6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -31,8 +30,6 @@ #include #include #include -#include -#include #include #include @@ -1088,38 +1085,7 @@ namespace MWRender void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) { - SceneUtil::FindByNameVisitor visitor("AttachLight"); - parent->accept(visitor); - - osg::Group* attachTo = NULL; - if (visitor.mFoundNode) - { - attachTo = visitor.mFoundNode; - } - else - { - osg::ComputeBoundsVisitor computeBound; - computeBound.setTraversalMask(~Mask_ParticleSystem); - parent->accept(computeBound); - - // PositionAttitudeTransform seems to be slightly faster than MatrixTransform - osg::ref_ptr trans(new osg::PositionAttitudeTransform); - trans->setPosition(computeBound.getBoundingBox().center()); - - parent->addChild(trans); - - attachTo = trans; - } - - osg::ref_ptr lightSource = new SceneUtil::LightSource; - osg::ref_ptr light (new osg::Light); - lightSource->setNodeMask(Mask_Lighting); - const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback(); - - float radius = esmLight->mData.mRadius; - lightSource->setRadius(radius); - static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); @@ -1127,38 +1093,10 @@ namespace MWRender static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); - bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - SceneUtil::configureLight(light, radius, exterior, outQuadInLin, useQuadratic, quadraticValue, - quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); - - osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); - if (esmLight->mData.mFlags & ESM::Light::Negative) - { - diffuse *= -1; - diffuse.a() = 1; - } - light->setDiffuse(diffuse); - light->setAmbient(osg::Vec4f(0,0,0,1)); - light->setSpecular(osg::Vec4f(0,0,0,0)); - - lightSource->setLight(light); - - osg::ref_ptr ctrl (new SceneUtil::LightController); - ctrl->setDiffuse(light->getDiffuse()); - if (esmLight->mData.mFlags & ESM::Light::Flicker) - ctrl->setType(SceneUtil::LightController::LT_Flicker); - if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) - ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); - if (esmLight->mData.mFlags & ESM::Light::Pulse) - ctrl->setType(SceneUtil::LightController::LT_Pulse); - if (esmLight->mData.mFlags & ESM::Light::PulseSlow) - ctrl->setType(SceneUtil::LightController::LT_PulseSlow); - - lightSource->addUpdateCallback(ctrl); - - attachTo->addChild(lightSource); + SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior, outQuadInLin, + useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 711a94ba8c..00f7f4d13a 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -1,6 +1,16 @@ #include "lightutil.hpp" #include +#include +#include + +#include + +#include "lightmanager.hpp" +#include "lightcontroller.hpp" +#include "util.hpp" +#include "visitor.hpp" +#include "positionattitudetransform.hpp" namespace SceneUtil { @@ -29,4 +39,69 @@ namespace SceneUtil } + void addLight (osg::Group* node, const ESM::Light* esmLight, int partsysMask, int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, + float linearValue) + { + SceneUtil::FindByNameVisitor visitor("AttachLight"); + node->accept(visitor); + + osg::Group* attachTo = NULL; + if (visitor.mFoundNode) + { + attachTo = visitor.mFoundNode; + } + else + { + osg::ComputeBoundsVisitor computeBound; + computeBound.setTraversalMask(~partsysMask); + node->accept(computeBound); + + // PositionAttitudeTransform seems to be slightly faster than MatrixTransform + osg::ref_ptr trans(new SceneUtil::PositionAttitudeTransform); + trans->setPosition(computeBound.getBoundingBox().center()); + + node->addChild(trans); + + attachTo = trans; + } + + osg::ref_ptr lightSource (new SceneUtil::LightSource); + osg::ref_ptr light (new osg::Light); + lightSource->setNodeMask(lightMask); + + float radius = esmLight->mData.mRadius; + lightSource->setRadius(radius); + + configureLight(light, radius, isExterior, outQuadInLin, useQuadratic, quadraticValue, + quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); + + osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); + if (esmLight->mData.mFlags & ESM::Light::Negative) + { + diffuse *= -1; + diffuse.a() = 1; + } + light->setDiffuse(diffuse); + light->setAmbient(osg::Vec4f(0,0,0,1)); + light->setSpecular(osg::Vec4f(0,0,0,0)); + + lightSource->setLight(light); + + osg::ref_ptr ctrl (new SceneUtil::LightController); + ctrl->setDiffuse(light->getDiffuse()); + if (esmLight->mData.mFlags & ESM::Light::Flicker) + ctrl->setType(SceneUtil::LightController::LT_Flicker); + if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) + ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); + if (esmLight->mData.mFlags & ESM::Light::Pulse) + ctrl->setType(SceneUtil::LightController::LT_Pulse); + if (esmLight->mData.mFlags & ESM::Light::PulseSlow) + ctrl->setType(SceneUtil::LightController::LT_PulseSlow); + + lightSource->addUpdateCallback(ctrl); + + attachTo->addChild(lightSource); + } + } diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 09d23d4a21..f7879cd72e 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -2,6 +2,11 @@ #define OPENMW_COMPONENTS_LIGHTUTIL_H namespace osg +{ + class Group; +} + +namespace ESM { class Light; } @@ -9,10 +14,18 @@ namespace osg namespace SceneUtil { - /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. - void configureLight(osg::Light* light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, - float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, - float linearValue); + /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and add it to a sub graph. + /// @note If the sub graph contains a node named "AttachLight" (case insensitive), then the light is added to that. + /// Otherwise, the light is added in the center of the node's bounds. + /// @param node The sub graph to add a light to + /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. + /// @param partsysMask Node mask to ignore when computing the sub graph's bounding box. + /// @param lightMask Mask to assign to the newly created LightSource. + /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. + /// @par Attenuation parameters come from the game INI file. + void addLight (osg::Group* node, const ESM::Light* esmLight, int partsysMask, int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, + float linearValue); } From ad2145b4634def65c7e7f8b7d2a2e5da15458242 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 14:34:39 +0100 Subject: [PATCH 083/765] OpenCS: use a separate enum for the toggle buttons --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/cell.cpp | 6 ++--- apps/opencs/view/render/cellarrow.cpp | 7 +++-- apps/opencs/view/render/elements.hpp | 23 ---------------- apps/opencs/view/render/instancemode.cpp | 6 ++--- apps/opencs/view/render/mask.hpp | 27 +++++++++++++++++++ apps/opencs/view/render/object.cpp | 7 +++-- .../view/render/pagedworldspacewidget.cpp | 26 +++++++++--------- apps/opencs/view/render/scenewidget.cpp | 6 ++--- apps/opencs/view/render/tagbase.cpp | 6 ++--- apps/opencs/view/render/tagbase.hpp | 8 +++--- .../view/render/unpagedworldspacewidget.cpp | 6 ++--- apps/opencs/view/render/worldspacewidget.cpp | 18 ++++++------- apps/opencs/view/render/worldspacewidget.hpp | 2 +- apps/opencs/view/widget/scenetooltoggle.cpp | 14 +++++----- apps/opencs/view/widget/scenetooltoggle.hpp | 10 +++---- apps/opencs/view/widget/scenetooltoggle2.cpp | 24 +++++++++++------ apps/opencs/view/widget/scenetooltoggle2.hpp | 21 +++++++++++---- 18 files changed, 120 insertions(+), 99 deletions(-) delete mode 100644 apps/opencs/view/render/elements.hpp create mode 100644 apps/opencs/view/render/mask.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0e9a49432b..8bb624704b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -94,7 +94,7 @@ opencs_units_noqt (view/render ) opencs_hdrs_noqt (view/render - elements + mask ) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index da40f7e7cb..40981164de 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -11,7 +11,7 @@ #include "../../model/world/refcollection.hpp" #include "../../model/world/cellcoordinates.hpp" -#include "elements.hpp" +#include "mask.hpp" #include "terrainstorage.hpp" bool CSVRender::Cell::removeObject (const std::string& id) @@ -80,7 +80,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Mask_Terrain)); mTerrain->loadCell(esmLand.mX, esmLand.mY); } @@ -230,7 +230,7 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int void CSVRender::Cell::setSelection (int elementMask, Selection mode) { - if (elementMask & Element_Reference) + if (elementMask & Mask_Reference) { for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index 1356aa0fbd..6d8fa1c6c1 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -7,10 +7,10 @@ #include #include -#include "elements.hpp" +#include "mask.hpp" CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) -: TagBase (Element_CellArrow), mArrow (arrow) +: TagBase (Mask_CellArrow), mArrow (arrow) {} CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const @@ -165,8 +165,7 @@ CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, mParentNode->addChild (mBaseNode); - // 0x1 reserved for separating cull and update visitors - mBaseNode->setNodeMask (Element_CellArrow<<1); + mBaseNode->setNodeMask (Mask_CellArrow); adjustTransform(); buildShape(); diff --git a/apps/opencs/view/render/elements.hpp b/apps/opencs/view/render/elements.hpp deleted file mode 100644 index 5a37956e9e..0000000000 --- a/apps/opencs/view/render/elements.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef CSV_RENDER_ELEMENTS_H -#define CSV_RENDER_ELEMENTS_H - -namespace CSVRender -{ - /// Visual elements in a scene - enum Elements - { - // elements that are part of the actual scene - Element_Reference = 0x1, - Element_Pathgrid = 0x2, - Element_Water = 0x4, - Element_Fog = 0x8, - Element_Terrain = 0x10, - - // control elements - Element_CellMarker = 0x10000, - Element_CellArrow = 0x20000, - Element_CellBorder = 0x40000 - }; -} - -#endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index a4d147bb4d..7f9e426c75 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -3,12 +3,12 @@ #include "../../model/prefs/state.hpp" -#include "elements.hpp" +#include "mask.hpp" #include "object.hpp" #include "worldspacewidget.hpp" CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", +: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", parent) { @@ -28,7 +28,7 @@ void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) { - getWorldspaceWidget().clearSelection (Element_Reference); + getWorldspaceWidget().clearSelection (Mask_Reference); if (tag) { diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp new file mode 100644 index 0000000000..9e63cc9770 --- /dev/null +++ b/apps/opencs/view/render/mask.hpp @@ -0,0 +1,27 @@ +#ifndef CSV_RENDER_ELEMENTS_H +#define CSV_RENDER_ELEMENTS_H + +namespace CSVRender +{ + + /// @note Enumeration values can be changed freely, as long as they do not collide. + enum Mask + { + // internal use within NifLoader, do not change + Mask_UpdateVisitor = 0x1, + + // elements that are part of the actual scene + Mask_Reference = 0x2, + Mask_Pathgrid = 0x4, + Mask_Water = 0x8, + Mask_Fog = 0x10, + Mask_Terrain = 0x20, + + // control elements + Mask_CellMarker = 0x10000, + Mask_CellArrow = 0x20000, + Mask_CellBorder = 0x40000 + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index c295a023a8..2ff80b38b5 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -19,7 +19,7 @@ #include #include -#include "elements.hpp" +#include "mask.hpp" namespace { @@ -39,7 +39,7 @@ namespace CSVRender::ObjectTag::ObjectTag (Object* object) -: TagBase (Element_Reference), mObject (object) +: TagBase (Mask_Reference), mObject (object) {} QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const @@ -138,8 +138,7 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, parentNode->addChild(mBaseNode); - // 0x1 reserved for separating cull and update visitors - mBaseNode->setNodeMask(Element_Reference<<1); + mBaseNode->setNodeMask(Mask_Reference); if (referenceable) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dae5990e6f..e0707524db 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -18,7 +18,7 @@ #include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" -#include "elements.hpp" +#include "mask.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { @@ -126,8 +126,8 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Element_Terrain, "Terrain"); - tool->addButton (Element_Fog, "Fog", "", true); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Terrain, Mask_Terrain, "Terrain"); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( @@ -137,22 +137,22 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( /// \todo replace EditMode with suitable subclasses tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain shape editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), "terrain-shape"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain texture editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain texture editing"), "terrain-texture"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain vertex paint editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Terrain movement"), + new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) { - if (tag && tag->getElement()==Element_CellArrow) + if (tag && tag->getMask()==Mask_CellArrow) { if (button=="p-edit" || button=="s-edit") { @@ -492,7 +492,7 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::g unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const { - return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelection(); + return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelectionMask(); } void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) @@ -510,12 +510,12 @@ CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibil mControlElements = new CSVWidget::SceneToolToggle (parent, "Controls & Guides Visibility", ":placeholder"); - mControlElements->addButton (":placeholder", Element_CellMarker, ":placeholder", + mControlElements->addButton (":placeholder", Mask_CellMarker, ":placeholder", "Cell marker"); - mControlElements->addButton (":placeholder", Element_CellArrow, ":placeholder", "Cell arrows"); - mControlElements->addButton (":placeholder", Element_CellBorder, ":placeholder", "Cell border"); + mControlElements->addButton (":placeholder", Mask_CellArrow, ":placeholder", "Cell arrows"); + mControlElements->addButton (":placeholder", Mask_CellBorder, ":placeholder", "Cell border"); - mControlElements->setSelection (0xffffffff); + mControlElements->setSelectionMask (0xffffffff); connect (mControlElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b1300a991f..d06a6b0011 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -18,6 +18,7 @@ #include "../widget/scenetoolmode.hpp" #include "lighting.hpp" +#include "mask.hpp" namespace CSVRender { @@ -73,7 +74,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) // Press S to reveal profiling stats mView->addEventHandler(new osgViewer::StatsHandler); - mView->getCamera()->setCullMask(~(0x1)); + mView->getCamera()->setCullMask(~(Mask_UpdateVisitor)); viewer.addView(mView); viewer.setDone(false); @@ -92,8 +93,7 @@ void RenderWidget::flagAsModified() void RenderWidget::setVisibilityMask(int mask) { - // 0x1 reserved for separating cull and update visitors - mView->getCamera()->setCullMask(mask<<1); + mView->getCamera()->setCullMask(mask); } bool RenderWidget::eventFilter(QObject* obj, QEvent* event) diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp index 79412c1321..3ddd68690f 100644 --- a/apps/opencs/view/render/tagbase.cpp +++ b/apps/opencs/view/render/tagbase.cpp @@ -1,11 +1,11 @@ #include "tagbase.hpp" -CSVRender::TagBase::TagBase (Elements element) : mElement (element) {} +CSVRender::TagBase::TagBase (Mask mask) : mMask (mask) {} -CSVRender::Elements CSVRender::TagBase::getElement() const +CSVRender::Mask CSVRender::TagBase::getMask() const { - return mElement; + return mMask; } QString CSVRender::TagBase::getToolTip (bool hideBasics) const diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp index 9f169c3b1a..d1ecd2cfd9 100644 --- a/apps/opencs/view/render/tagbase.hpp +++ b/apps/opencs/view/render/tagbase.hpp @@ -5,19 +5,19 @@ #include -#include "elements.hpp" +#include "mask.hpp" namespace CSVRender { class TagBase : public osg::Referenced { - Elements mElement; + Mask mMask; public: - TagBase (Elements element); + TagBase (Mask mask); - Elements getElement() const; + Mask getMask() const; virtual QString getToolTip (bool hideBasics) const; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 68f068daca..4b2ff9a5f0 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -17,7 +17,7 @@ #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetooltoggle2.hpp" -#include "elements.hpp" +#include "mask.hpp" void CSVRender::UnpagedWorldspaceWidget::update() { @@ -166,8 +166,8 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Element_Terrain, "Terrain", "", true); - tool->addButton (Element_Fog, "Fog"); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Terrain, Mask_Terrain, "Terrain", "", true); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index f0d3986414..3b169b5c29 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -27,7 +27,7 @@ #include "../widget/scenetoolrun.hpp" #include "object.hpp" -#include "elements.hpp" +#include "mask.hpp" #include "editmode.hpp" #include "instancemode.hpp" @@ -160,7 +160,7 @@ CSVWidget::SceneToolToggle2 *CSVRender::WorldspaceWidget::makeSceneVisibilitySel addVisibilitySelectorButtons (mSceneElements); - mSceneElements->setSelection (0xffffffff); + mSceneElements->setSelectionMask (0xffffffff); connect (mSceneElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); @@ -275,12 +275,12 @@ bool CSVRender::WorldspaceWidget::handleDrop (const std::vectorgetSelection(); + return mSceneElements->getSelectionMask(); } void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) { - mInteractionMask = mask | Element_CellMarker | Element_CellArrow; + mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; } unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const @@ -296,9 +296,9 @@ void CSVRender::WorldspaceWidget::setEditLock (bool locked) void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { - tool->addButton (Element_Reference, "Instances"); - tool->addButton (Element_Water, "Water"); - tool->addButton (Element_Pathgrid, "Pathgrid"); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Reference, Mask_Reference, "Instances"); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Water, Mask_Water, "Water"); + tool->addButton (CSVWidget::SceneToolToggle2::Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) @@ -306,7 +306,7 @@ void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneTo /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, tool), "object"); tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Pathgrid, "Pathgrid editing"), + new EditMode (this, QIcon (":placeholder"), Mask_Pathgrid, "Pathgrid editing"), "pathgrid"); } @@ -404,7 +404,7 @@ osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const Q intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); - visitor.setTraversalMask(getInteractionMask() << 1); + visitor.setTraversalMask(getInteractionMask()); mView->getCamera()->accept(visitor); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 54376cee45..07e505fe2c 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -11,7 +11,7 @@ #include "../../model/world/tablemimedata.hpp" #include "scenewidget.hpp" -#include "elements.hpp" +#include "mask.hpp" namespace CSMPrefs { diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index d7251882a7..5919a280af 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -40,7 +40,7 @@ void CSVWidget::SceneToolToggle::adjustToolTip() void CSVWidget::SceneToolToggle::adjustIcon() { - unsigned int selection = getSelection(); + unsigned int selection = getSelectionMask(); if (!selection) setIcon (QIcon (QString::fromUtf8 (mEmptyIcon.c_str()))); else @@ -135,7 +135,7 @@ void CSVWidget::SceneToolToggle::showPanel (const QPoint& position) mFirst->setFocus (Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int id, +void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip) { if (mButtons.size()>=9) @@ -151,7 +151,7 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in mLayout->addWidget (button); ButtonDesc desc; - desc.mId = id; + desc.mMask = mask; desc.mSmallIcon = smallIcon; desc.mName = name; desc.mIndex = mButtons.size(); @@ -164,23 +164,23 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in mFirst = button; } -unsigned int CSVWidget::SceneToolToggle::getSelection() const +unsigned int CSVWidget::SceneToolToggle::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) - selection |= iter->second.mId; + selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle::setSelection (unsigned int selection) +void CSVWidget::SceneToolToggle::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mId); + iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); diff --git a/apps/opencs/view/widget/scenetooltoggle.hpp b/apps/opencs/view/widget/scenetooltoggle.hpp index 55e6975249..68cd2362e6 100644 --- a/apps/opencs/view/widget/scenetooltoggle.hpp +++ b/apps/opencs/view/widget/scenetooltoggle.hpp @@ -20,7 +20,7 @@ namespace CSVWidget struct ButtonDesc { - unsigned int mId; + unsigned int mMask; std::string mSmallIcon; QString mName; int mIndex; @@ -54,13 +54,13 @@ namespace CSVWidget /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An /// attempt to add more will result in an exception being thrown. /// The small icons will be sized at (x-4)/3 (where x is the main icon size). - void addButton (const std::string& icon, unsigned int id, + void addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip = ""); - unsigned int getSelection() const; + unsigned int getSelectionMask() const; - /// \param or'ed button IDs. IDs that do not exist will be ignored. - void setSelection (unsigned int selection); + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask (unsigned int selection); signals: diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index e0431476eb..3972aadaa3 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -41,8 +41,15 @@ void CSVWidget::SceneToolToggle2::adjustToolTip() void CSVWidget::SceneToolToggle2::adjustIcon() { + unsigned int buttonIds = 0; + + for (std::map::const_iterator iter (mButtons.begin()); + iter!=mButtons.end(); ++iter) + if (iter->first->isChecked()) + buttonIds |= iter->second.mButtonId; + std::ostringstream stream; - stream << mCompositeIcon << getSelection(); + stream << mCompositeIcon << buttonIds; setIcon (QIcon (QString::fromUtf8 (stream.str().c_str()))); } @@ -70,11 +77,11 @@ void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) mFirst->setFocus (Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle2::addButton (unsigned int id, +void CSVWidget::SceneToolToggle2::addButton (ButtonId id, unsigned int mask, const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; - stream << mSingleIcon << id; + stream << mSingleIcon << static_cast(id); PushButton *button = new PushButton (QIcon (QPixmap (stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); @@ -89,7 +96,8 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, mLayout->addWidget (button); ButtonDesc desc; - desc.mId = id; + desc.mButtonId = id; + desc.mMask = mask; desc.mName = name; desc.mIndex = mButtons.size(); @@ -101,23 +109,23 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, mFirst = button; } -unsigned int CSVWidget::SceneToolToggle2::getSelection() const +unsigned int CSVWidget::SceneToolToggle2::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) - selection |= iter->second.mId; + selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle2::setSelection (unsigned int selection) +void CSVWidget::SceneToolToggle2::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mId); + iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); diff --git a/apps/opencs/view/widget/scenetooltoggle2.hpp b/apps/opencs/view/widget/scenetooltoggle2.hpp index 0bae780f97..eea9c3ebc1 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.hpp +++ b/apps/opencs/view/widget/scenetooltoggle2.hpp @@ -22,7 +22,8 @@ namespace CSVWidget struct ButtonDesc { - unsigned int mId; + unsigned int mButtonId; + unsigned int mMask; QString mName; int mIndex; }; @@ -53,15 +54,25 @@ namespace CSVWidget virtual void showPanel (const QPoint& position); + /// Visual elements in a scene + /// @note do not change the enumeration values, they are used in pre-existing button file names! + enum ButtonId + { + Button_Reference = 0x1, + Button_Pathgrid = 0x2, + Button_Water = 0x4, + Button_Fog = 0x8, + Button_Terrain = 0x10 + }; /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. - void addButton (unsigned int id, + void addButton (ButtonId buttonId, unsigned int mask, const QString& name, const QString& tooltip = "", bool disabled = false); - unsigned int getSelection() const; + unsigned int getSelectionMask() const; - /// \param or'ed button IDs. IDs that do not exist will be ignored. - void setSelection (unsigned int selection); + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask (unsigned int selection); signals: From e7bb8878f3f0a162e89f804e49014615a1894251 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 14:39:48 +0100 Subject: [PATCH 084/765] OpenCS: add a mask for particle systems --- apps/opencs/view/render/mask.hpp | 3 +++ apps/opencs/view/render/scenewidget.cpp | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 9e63cc9770..984dccae4a 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -17,6 +17,9 @@ namespace CSVRender Mask_Fog = 0x10, Mask_Terrain = 0x20, + // used within models + Mask_ParticleSystem = 0x100, + // control elements Mask_CellMarker = 0x10000, Mask_CellArrow = 0x20000, diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index d06a6b0011..d7138b392b 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -93,7 +93,7 @@ void RenderWidget::flagAsModified() void RenderWidget::setVisibilityMask(int mask) { - mView->getCamera()->setCullMask(mask); + mView->getCamera()->setCullMask(mask | Mask_ParticleSystem); } bool RenderWidget::eventFilter(QObject* obj, QEvent* event) @@ -167,6 +167,8 @@ SceneWidget::SceneWidget(boost::shared_ptr resourceSys setLighting(&mLightingDay); + mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); + /// \todo make shortcut configurable QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); From 7f967153ef993752c65bcda70bb4724eb76025af Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 15:00:58 +0100 Subject: [PATCH 085/765] OpenCS: create light sources --- apps/opencs/view/render/object.cpp | 30 ++++++++++++++++++++++++- apps/opencs/view/render/scenewidget.cpp | 5 ++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 2ff80b38b5..f202be4bc7 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -17,7 +17,9 @@ #include "../../model/world/refidcollection.hpp" #include -#include +#include +#include +#include #include "mask.hpp" @@ -62,6 +64,7 @@ void CSVRender::Object::update() const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); int index = referenceables.searchId (mReferenceableId); + const ESM::Light* light = NULL; if (index==-1) error = 1; @@ -73,6 +76,14 @@ void CSVRender::Object::update() referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). toString().toUtf8().constData(); + int recordType = + referenceables.getData (index, + referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt(); + if (recordType == CSMWorld::UniversalId::Type_Light) + { + light = &dynamic_cast& >(referenceables.getRecord(index)).get(); + } + if (model.empty()) error = 2; } @@ -97,6 +108,21 @@ void CSVRender::Object::update() std::cerr << e.what() << std::endl; } } + + if (light) + { + const Fallback::Map* fallback = mData.getFallbackMap(); + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + bool isExterior = false; // FIXME + SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, ~0u, isExterior, outQuadInLin, useQuadratic, + quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); + } } void CSVRender::Object::adjustTransform() @@ -131,6 +157,8 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, : mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero) { mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->addCullCallback(new SceneUtil::LightListCallback); + mOutline = new osgFX::Scribe; mOutline->addChild(mBaseNode); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index d7138b392b..c51b22e090 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "../widget/scenetoolmode.hpp" @@ -64,7 +65,9 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast(traits->width)/static_cast(traits->height), 1.0f, 10000.0f ); - mRootNode = new osg::Group; + SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + lightMgr->setStartLight(1); + mRootNode = lightMgr; mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); From 93cc08a09cd5f132e7e79ad28737c33df7852d9d Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 23:14:32 +0100 Subject: [PATCH 086/765] Lighting fix for LightListCallbacks attached to a Transform node --- components/sceneutil/lightmanager.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f9182c1d2d..64448e73e9 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -309,8 +309,15 @@ namespace SceneUtil const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); - // we do the intersections in view space - osg::BoundingSphere nodeBound = node->getBound(); + // get the node bounds in view space + // NB do not node->getBound() * modelView, that would apply the node's transformation twice + osg::BoundingSphere nodeBound; + osg::Group* group = node->asGroup(); + if (group) + { + for (unsigned int i=0; igetNumChildren(); ++i) + nodeBound.expandBy(group->getChild(i)->getBound()); + } osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); From e1c7165bfb37c881aeb753dd67479501620d14fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 23:16:47 +0100 Subject: [PATCH 087/765] Fix bounds calculation for addLight to a transform node --- components/sceneutil/lightutil.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 00f7f4d13a..725157e230 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -55,7 +55,9 @@ namespace SceneUtil { osg::ComputeBoundsVisitor computeBound; computeBound.setTraversalMask(~partsysMask); - node->accept(computeBound); + // We want the bounds of all children of the node, ignoring the node's local transformation + // So do a traverse(), not accept() + computeBound.traverse(*node); // PositionAttitudeTransform seems to be slightly faster than MatrixTransform osg::ref_ptr trans(new SceneUtil::PositionAttitudeTransform); From 458a305bff2bcdd9d82d347a13539c537cf14628 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Jan 2016 23:20:26 +0100 Subject: [PATCH 088/765] OpenCS: add lighting mask --- apps/opencs/view/render/mask.hpp | 2 ++ apps/opencs/view/render/object.cpp | 2 +- apps/opencs/view/render/scenewidget.cpp | 3 ++- components/sceneutil/lightutil.cpp | 2 +- components/sceneutil/lightutil.hpp | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 984dccae4a..d2dfef7b47 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -20,6 +20,8 @@ namespace CSVRender // used within models Mask_ParticleSystem = 0x100, + Mask_Lighting = 0x200, + // control elements Mask_CellMarker = 0x10000, Mask_CellArrow = 0x20000, diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index f202be4bc7..1821da059e 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -120,7 +120,7 @@ void CSVRender::Object::update() static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); bool isExterior = false; // FIXME - SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, ~0u, isExterior, outQuadInLin, useQuadratic, + SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, Mask_Lighting, isExterior, outQuadInLin, useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index c51b22e090..e5b9171e09 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -67,6 +67,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); + lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); @@ -96,7 +97,7 @@ void RenderWidget::flagAsModified() void RenderWidget::setVisibilityMask(int mask) { - mView->getCamera()->setCullMask(mask | Mask_ParticleSystem); + mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); } bool RenderWidget::eventFilter(QObject* obj, QEvent* event) diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 725157e230..6499c54b1d 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -39,7 +39,7 @@ namespace SceneUtil } - void addLight (osg::Group* node, const ESM::Light* esmLight, int partsysMask, int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) { diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index f7879cd72e..810ffa3187 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -23,7 +23,7 @@ namespace SceneUtil /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. /// @par Attenuation parameters come from the game INI file. - void addLight (osg::Group* node, const ESM::Light* esmLight, int partsysMask, int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool outQuadInLin, bool useQuadratic, float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue); From 4e6a60672d3cc265f7532433f79f73eeb2e8bf03 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Jan 2016 00:31:34 +0100 Subject: [PATCH 089/765] When a spell explodes on an actor do not apply it to that actor twice (Fixes #3142) --- apps/openmw/mwbase/world.hpp | 4 ++-- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 3 ++- apps/openmw/mwworld/worldimp.cpp | 7 +++++-- apps/openmw/mwworld/worldimp.hpp | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 462d08727c..a1580c434f 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -521,8 +521,8 @@ namespace MWBase virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; - virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, + const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ab8784beb1..6073076e00 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -552,7 +552,7 @@ namespace MWMechanics } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, range, mId, mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName); if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 439ac19ec9..d1faf621c2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -237,7 +237,8 @@ namespace MWWorld if (hit) { - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, ESM::RT_Target, it->mSpellId, it->mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, + ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); mParent->removeChild(it->mNode); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 19d90e2096..810eff643e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3072,8 +3072,8 @@ namespace MWWorld mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, ESM::RangeType rangeType, - const std::string& id, const std::string& sourceName) + void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, const Ptr& ignore, + ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) { std::map > toApply; for (std::vector::const_iterator effectIt = effects.mList.begin(); @@ -3121,6 +3121,9 @@ namespace MWWorld if (apply->first == caster) continue; + if (apply->first == ignore) + continue; + if (source.isEmpty()) source = apply->first; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 5f0507acef..6eed4e4845 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -623,8 +623,8 @@ namespace MWWorld virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos); - virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, + const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); From ef1a1125e028573931604f5b459ec0f07d31ed28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Jan 2016 17:10:23 +0100 Subject: [PATCH 090/765] Fix an error in restocking logic (Fixes #3131) --- apps/openmw/mwworld/containerstore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index ab9fa46110..45390405e0 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -529,10 +529,10 @@ void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MW { std::map::iterator listInMap = allowedForReplace.find(itemOrList); - int restockNum = it->mCount; + int restockNum = std::abs(it->mCount); //If we know we must restock less, take it into account if(listInMap != allowedForReplace.end()) - restockNum += listInMap->second;//We add, because list items have negative count + restockNum -= std::min(restockNum, listInMap->second); //restock addInitialItem(itemOrList, owner, restockNum, true); } From 471ad3fb0f0f4b332057e3fa7ee277dc780abcc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Jan 2016 17:10:23 +0100 Subject: [PATCH 091/765] Fix an error in restocking logic (Fixes #3131) --- apps/openmw/mwworld/containerstore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index ab9fa46110..45390405e0 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -529,10 +529,10 @@ void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MW { std::map::iterator listInMap = allowedForReplace.find(itemOrList); - int restockNum = it->mCount; + int restockNum = std::abs(it->mCount); //If we know we must restock less, take it into account if(listInMap != allowedForReplace.end()) - restockNum += listInMap->second;//We add, because list items have negative count + restockNum -= std::min(restockNum, listInMap->second); //restock addInitialItem(itemOrList, owner, restockNum, true); } From 2ce2e8a1e7883fced661bd37bbbdba68ce389edd Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Mon, 11 Jan 2016 07:24:52 -0600 Subject: [PATCH 092/765] Add missing OOB slot check to InventoryStore. --- apps/openmw/mwworld/inventorystore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index b82b798d11..dbd2c67940 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -523,6 +523,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) { + if (slot<0 || slot>=static_cast (mSlots.size())) + throw std::runtime_error ("slot number out of range"); + ContainerStoreIterator it = mSlots[slot]; if (it != end()) From 6eba647a9d4b88925997aba9f2495f367857a519 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Jan 2016 13:19:37 +0100 Subject: [PATCH 093/765] added accessor function for cell selection in paged worldspace --- apps/opencs/view/render/pagedworldspacewidget.cpp | 5 +++++ apps/opencs/view/render/pagedworldspacewidget.hpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index e30f238a29..414076cea3 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -432,6 +432,11 @@ void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSel emit cellSelectionChanged (mSelection); } +const CSMWorld::CellSelection& CSVRender::PagedWorldspaceWidget::getCellSelection() const +{ + return mSelection; +} + std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (const std::string& record) const { std::istringstream stream (record.c_str()); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index bc8e0da646..647341d1f4 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -80,6 +80,8 @@ namespace CSVRender void setCellSelection(const CSMWorld::CellSelection& selection); + const CSMWorld::CellSelection& getCellSelection() const; + /// \return Drop handled? virtual bool handleDrop (const std::vector& data, DropType type); From 55627c08532356e348e115af58da90f9bb55af7d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Jan 2016 13:20:01 +0100 Subject: [PATCH 094/765] handling drops into cells that do not exist or are not shown --- apps/opencs/model/prefs/state.cpp | 13 ++++ apps/opencs/view/render/instancemode.cpp | 84 ++++++++++++++++++++---- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d2ad0f6ef2..859fabd117 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -191,10 +191,23 @@ void CSMPrefs::State::declare() declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). setMin (1); + EnumValue createAndInsert ("Create cell and insert"); + EnumValue showAndInsert ("Show cell and insert"); + EnumValue dontInsert ("Discard"); + EnumValue insertAnyway ("Insert anyway"); + EnumValues insertOutsideCell; + insertOutsideCell.add (createAndInsert).add (dontInsert).add (insertAnyway); + EnumValues insertOutsideVisibleCell; + insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); + declareCategory ("Scene Drops"); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); + declareEnum ("outside-drop", "Handling drops outside of cells", createAndInsert). + addValues (insertOutsideCell); + declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). + addValues (insertOutsideVisibleCell); } void CSMPrefs::State::declareCategory (const std::string& key) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 6c3593369c..b6430036b4 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -6,11 +6,13 @@ #include "../../model/prefs/state.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "elements.hpp" #include "object.hpp" #include "worldspacewidget.hpp" +#include "pagedworldspacewidget.hpp" CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", @@ -68,7 +70,6 @@ void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) if (!mime->fromDocument (getWorldspaceWidget().getDocument())) return; - /// \todo document check if (mime->holdsType (CSMWorld::UniversalId::Type_Referenceable)) event->accept(); } @@ -87,15 +88,68 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) std::string cellId = getWorldspaceWidget().getCellId (insertPoint); - bool dropped = false; + CSMWorld::IdTree& cellTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - std::vector ids = mime->getData(); + bool noCell = document.getData().getCells().searchId (cellId)==-1; + + if (noCell) + { + std::string mode = CSMPrefs::get()["Scene Drops"]["outside-drop"].toString(); + + // target cell does not exist + if (mode=="Discard") + return; + + if (mode=="Create cell and insert") + { + std::auto_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); + } + + noCell = false; + } + } + 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()["Scene Drops"]["outside-visible-drop"].toString(); + + if (mode=="Discard") + return; + + if (mode=="Show cell and insert") + { + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } CSMWorld::IdTable& referencesTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); - CSMWorld::IdTable& cellTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + bool dropped = false; + + std::vector ids = mime->getData(); for (std::vector::const_iterator iter (ids.begin()); iter!=ids.end(); ++iter) @@ -118,18 +172,24 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) CSMWorld::Columns::ColumnId_ReferenceableId), QString::fromUtf8 (iter->getId().c_str())); - // increase reference count in cell - QModelIndex countIndex = cellTable.getModelIndex (cellId, - cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter)); + std::auto_ptr incrementCommand; - int count = cellTable.data (countIndex).toInt(); + if (!noCell) + { + // increase reference count in cell + QModelIndex countIndex = cellTable.getModelIndex (cellId, + cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter)); - std::auto_ptr incrementCommand ( - new CSMWorld::ModifyCommand (cellTable, countIndex, count+1)); + int count = cellTable.data (countIndex).toInt(); + + incrementCommand.reset ( + new CSMWorld::ModifyCommand (cellTable, countIndex, count+1)); + } document.getUndoStack().beginMacro (createCommand->text()); document.getUndoStack().push (createCommand.release()); - document.getUndoStack().push (incrementCommand.release()); + if (incrementCommand.get()) + document.getUndoStack().push (incrementCommand.release()); document.getUndoStack().endMacro(); dropped = true; From 62fe47b144d8312f90b5d773c31712d876f76779 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Jan 2016 15:37:43 +0100 Subject: [PATCH 095/765] Load default terrain if there is none defined --- components/esmterrain/storage.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index f0865a0a98..4be56d5e6a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -14,6 +14,8 @@ namespace ESMTerrain { + const float defaultHeight = -2048; + Storage::Storage(const VFS::Manager *vfs) : mVFS(vfs) { @@ -62,7 +64,9 @@ namespace ESMTerrain return true; } - return false; + min = defaultHeight; + max = defaultHeight; + return true; } void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row) @@ -203,7 +207,7 @@ namespace ESMTerrain assert (vertX < numVerts); assert (vertY < numVerts); - float height = -2048; + float height = defaultHeight; if (heightData) height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; @@ -412,7 +416,7 @@ namespace ESMTerrain const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return -2048; + return defaultHeight; // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition From 091ca9743e97f16368c028d15ab00ec113e50b64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Jan 2016 16:41:29 +0100 Subject: [PATCH 096/765] Do not write LandData twice (Fixes #3140) --- apps/opencs/model/doc/savingstages.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index db38c4779b..af9c380a31 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -394,10 +394,6 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) CSMWorld::Land record = land.get(); writer.startRecord (record.sRecordId); record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); - - if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) - data->save (mState.getWriter()); - writer.endRecord (record.sRecordId); } } From 0b84b3c2cf3253b0cf6c514485fd4601677859a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Jan 2016 17:41:39 +0100 Subject: [PATCH 097/765] Don't crash when region weather chances don't add to 100 The invalid weather ID was resulting in out-of-bounds vector access later in the code. --- apps/openmw/mwworld/weather.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5ce3d5c2fe..321637194e 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -321,10 +321,14 @@ void RegionWeather::chooseNewWeather() { sum += mChances[i]; if(chance <= sum) - break; + { + mWeather = i; + return; + } } - mWeather = i; + // if we hit this path then the chances don't add to 100, choose a default weather instead + mWeather = 0; } MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& fallback) From 4f2a031f4e66e50946ada6b20ada8f03d981fc6c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Jan 2016 18:34:47 +0100 Subject: [PATCH 098/765] Improve error message --- apps/openmw/mwworld/containerstore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 45390405e0..d8c37061f9 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -627,7 +627,7 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) return Type_Weapon; throw std::runtime_error ( - "Object of type " + ptr.getTypeName() + " can not be placed into a container"); + "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) From c1901069be63ecd09b6c5d34d3e56807c3c05bb2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 15 Jan 2016 12:07:25 +0100 Subject: [PATCH 099/765] added instance mode sub-modes --- apps/opencs/view/render/instancemode.cpp | 43 +++++++++++++++++++++++- apps/opencs/view/render/instancemode.hpp | 10 ++++++ apps/opencs/view/world/scenesubview.cpp | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index b6430036b4..449d9d7a6f 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -9,6 +9,9 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolmode.hpp" + #include "elements.hpp" #include "object.hpp" #include "worldspacewidget.hpp" @@ -16,9 +19,47 @@ CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", - parent) + parent), mSubMode (0) { +} +void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) +{ + if (!mSubMode) + { + mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); + mSubMode->addButton (":placeholder", "move", + "Move selected instances" + "
  • Use primary edit to move instances around freely
  • " + "
  • Use secondary edit to move instances around within the grid
  • " + "
" + "Not implemented yet"); + mSubMode->addButton (":placeholder", "rotate", + "Rotate selected instances" + "
  • Use primary edit to rotate instances freely
  • " + "
  • Use secondary edit to rotate instances within the grid
  • " + "
" + "Not implemented yet"); + mSubMode->addButton (":placeholder", "scale", + "Scale selected instances" + "
  • Use primary edit to scale instances freely
  • " + "
  • Use secondary edit to scale instances along the grid
  • " + "
" + "Not implemented yet"); + } + + EditMode::activate (toolbar); + + toolbar->addTool (mSubMode); +} + +void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) +{ + toolbar->removeTool (mSubMode); + delete mSubMode; + mSubMode = 0; + + EditMode::deactivate (toolbar); } void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 7649c241ca..1eec62874d 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -3,16 +3,26 @@ #include "editmode.hpp" +namespace CSVWidget +{ + class SceneToolMode; +} + namespace CSVRender { class InstanceMode : public EditMode { Q_OBJECT + CSVWidget::SceneToolMode *mSubMode; public: InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); + virtual void activate (CSVWidget::SceneToolbar *toolbar); + + virtual void deactivate (CSVWidget::SceneToolbar *toolbar); + virtual void primaryEditPressed (osg::ref_ptr tag); virtual void secondaryEditPressed (osg::ref_ptr tag); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 44fe94d840..7014b14864 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -122,7 +122,7 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); toolbar->addTool (runTool); - toolbar->addTool (widget->makeEditModeSelector (toolbar)); + toolbar->addTool (widget->makeEditModeSelector (toolbar), runTool); return toolbar; } From 18320b2cd03052dc70d8d710a91d8dd1e8bd2080 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Jan 2016 15:49:10 +0100 Subject: [PATCH 100/765] Move the Button enum to WorldSpaceWidget --- apps/opencs/view/render/pagedworldspacewidget.cpp | 4 ++-- .../opencs/view/render/unpagedworldspacewidget.cpp | 4 ++-- apps/opencs/view/render/worldspacewidget.cpp | 6 +++--- apps/opencs/view/render/worldspacewidget.hpp | 11 +++++++++++ apps/opencs/view/widget/scenetooltoggle2.cpp | 4 ++-- apps/opencs/view/widget/scenetooltoggle2.hpp | 14 +++----------- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index c6e422bd10..1880beab8b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -126,8 +126,8 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Fog, Mask_Fog, "Fog", "", true); + tool->addButton (Button_Terrain, Mask_Terrain, "Terrain"); + tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index e203a250ba..dad37c946b 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -171,8 +171,8 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Fog, Mask_Fog, "Fog"); + tool->addButton (Button_Terrain, Mask_Terrain, "Terrain", "", true); + tool->addButton (Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 800fd3d6e4..184477d6b2 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -296,9 +296,9 @@ void CSVRender::WorldspaceWidget::setEditLock (bool locked) void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { - tool->addButton (CSVWidget::SceneToolToggle2::Button_Reference, Mask_Reference, "Instances"); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Water, Mask_Water, "Water"); - tool->addButton (CSVWidget::SceneToolToggle2::Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); + tool->addButton (Button_Reference, Mask_Reference, "Instances"); + tool->addButton (Button_Water, Mask_Water, "Water"); + tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 142ed04ca0..7a77edad42 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -142,6 +142,17 @@ namespace CSVRender protected: + /// Visual elements in a scene + /// @note do not change the enumeration values, they are used in pre-existing button file names! + enum ButtonId + { + Button_Reference = 0x1, + Button_Pathgrid = 0x2, + Button_Water = 0x4, + Button_Fog = 0x8, + Button_Terrain = 0x10 + }; + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 3972aadaa3..720da6a964 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -77,11 +77,11 @@ void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) mFirst->setFocus (Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle2::addButton (ButtonId id, unsigned int mask, +void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; - stream << mSingleIcon << static_cast(id); + stream << mSingleIcon << id; PushButton *button = new PushButton (QIcon (QPixmap (stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); diff --git a/apps/opencs/view/widget/scenetooltoggle2.hpp b/apps/opencs/view/widget/scenetooltoggle2.hpp index eea9c3ebc1..50337ac117 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.hpp +++ b/apps/opencs/view/widget/scenetooltoggle2.hpp @@ -54,19 +54,11 @@ namespace CSVWidget virtual void showPanel (const QPoint& position); - /// Visual elements in a scene - /// @note do not change the enumeration values, they are used in pre-existing button file names! - enum ButtonId - { - Button_Reference = 0x1, - Button_Pathgrid = 0x2, - Button_Water = 0x4, - Button_Fog = 0x8, - Button_Terrain = 0x10 - }; + /// \param buttonId used to compose the icon filename + /// \param mask used for the reported getSelectionMask() / setSelectionMask() /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. - void addButton (ButtonId buttonId, unsigned int mask, + void addButton (unsigned int buttonId, unsigned int mask, const QString& name, const QString& tooltip = "", bool disabled = false); unsigned int getSelectionMask() const; From fbf07133ead40924b7ed57191505627fb632279c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Jan 2016 16:31:24 +0100 Subject: [PATCH 101/765] Document usage of node masks --- apps/opencs/view/render/mask.hpp | 5 ++++- apps/openmw/mwrender/vismask.hpp | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index d2dfef7b47..55b7c823f3 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -4,7 +4,10 @@ namespace CSVRender { - /// @note Enumeration values can be changed freely, as long as they do not collide. + /// Node masks used on the OSG scene graph in OpenMW-CS. + /// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp) + /// for general usage hints about node masks. + /// @copydoc MWRender::VisMask enum Mask { // internal use within NifLoader, do not change diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index dd6e85e2ca..81bb2f3440 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -5,6 +5,20 @@ namespace MWRender { /// Node masks used for controlling visibility of game objects. + /// @par Any node in the OSG scene graph can have a node mask. When traversing the scene graph, + /// the node visitor's traversal mask is OR'ed with the node mask. If the result of this test is + /// 0, then the node and all its child nodes are not processed. + /// @par Important traversal masks are the camera's cull mask (determines what is visible), + /// the update visitor mask (what is updated) and the intersection visitor mask (what is + /// selectable through mouse clicks or other intersection tests). + /// @par In practice, it can be useful to make a "hierarchy" out of the node masks - e.g. in OpenMW, + /// all 3D rendering nodes are child of a Scene Root node with Mask_Scene. When we do not want 3D rendering, + /// we can just omit Mask_Scene from the traversal mask, and do not need to omit all the individual + /// element masks (water, sky, terrain, etc.) since the traversal will already have stopped at the Scene root node. + /// @par The comments within the VisMask enum should give some hints as to what masks are commonly "child" of + /// another mask, or what type of node this mask is usually set on. + /// @note The mask values are not serialized within models, nor used in any other way that would break backwards + /// compatibility if the enumeration values were to be changed. Feel free to change them when it makes sense. enum VisMask { Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors @@ -24,8 +38,6 @@ namespace MWRender Mask_Sun = (1<<10), Mask_WeatherParticles = (1<<11), - // child of Water - // top level masks Mask_Scene = (1<<12), Mask_GUI = (1<<13), From 87beb7397070d65c3bf37d6b864f2bb904df69f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Jan 2016 16:34:57 +0100 Subject: [PATCH 102/765] Correction --- apps/openmw/mwrender/vismask.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index 81bb2f3440..ce13758e32 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -6,7 +6,7 @@ namespace MWRender /// Node masks used for controlling visibility of game objects. /// @par Any node in the OSG scene graph can have a node mask. When traversing the scene graph, - /// the node visitor's traversal mask is OR'ed with the node mask. If the result of this test is + /// the node visitor's traversal mask is bitwise AND'ed with the node mask. If the result of this test is /// 0, then the node and all its child nodes are not processed. /// @par Important traversal masks are the camera's cull mask (determines what is visible), /// the update visitor mask (what is updated) and the intersection visitor mask (what is From a5411c1ec2551c4789a9ac17c84d147b3bea8d41 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Fri, 15 Jan 2016 20:51:39 -0500 Subject: [PATCH 103/765] Remove unused code --- apps/launcher/main.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 282b3fb89f..eadec64d0a 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -51,10 +51,6 @@ int main(int argc, char *argv[]) if (result == Launcher::FirstRunDialogResultFailure) return 0; - // if (!mainWin.setup()) { - // return 0; - // } - if (result == Launcher::FirstRunDialogResultContinue) mainWin.show(); From e30a38a4c1cd13c1312112a44800d2222f99d367 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 16 Jan 2016 10:55:05 +0100 Subject: [PATCH 104/765] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index d4927ea92f..ce27280104 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -96,6 +96,7 @@ Programmers Pieter van der Kloet (pvdk) pkubik Radu-Marius Popovici (rpopovici) + rcutmore rdimesio riothamus Robert MacGregor (Ragora) From 21d13a48faeb91a42634a288e66fd25e82ea00aa Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sat, 16 Jan 2016 06:43:45 -0500 Subject: [PATCH 105/765] Make setup method of MainDialog class private --- apps/launcher/maindialog.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 0dfea48659..96b5c0b979 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -50,7 +50,6 @@ namespace Launcher explicit MainDialog(QWidget *parent = 0); ~MainDialog(); - bool setup(); FirstRunDialogResult showFirstRunDialog(); bool reloadSettings(); @@ -65,6 +64,8 @@ namespace Launcher void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); private: + bool setup(); + void createIcons(); void createPages(); From 97bcdf7904e2146ff0167710c7ebd658a2f5696b Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Sat, 16 Jan 2016 15:57:03 -0600 Subject: [PATCH 106/765] Properly find MyGUI in /usr/local/lib --- cmake/FindMyGUI.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 61a39e4d3a..85a1055f6b 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -101,7 +101,7 @@ ELSE (WIN32) #Unix SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") ELSE (MYGUI_INCLUDE_DIRS) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) - FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) + FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngine PATHS /usr/local/lib /usr/lib) SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") From 259d51058a7cad81e4a121a14765e23290e91289 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Jan 2016 00:09:58 +0100 Subject: [PATCH 107/765] Update AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index ce27280104..bfbd14a5b8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -48,6 +48,7 @@ Programmers Gašper Sedej gugus/gus Hallfaer Tuilinn + hristoast Internecine Jacob Essex (Yacoby) Jannik Heller (scrawl) From 08b469c0d004cb87c9a5f90e32ca098a7e43f635 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 17 Jan 2016 14:18:49 -0500 Subject: [PATCH 108/765] Remove unused forward declarations in filedialog.hpp --- apps/opencs/view/doc/filedialog.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 6488365652..ca6145b9c6 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -14,9 +14,6 @@ Q_DECLARE_METATYPE (boost::filesystem::path) #include "ui_filedialog.h" -class DataFilesModel; -class PluginsProxyModel; - namespace ContentSelectorView { class ContentSelector; From 1d86f705baab74a592c4711689cc307a41e2518c Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Sun, 17 Jan 2016 21:55:03 -0500 Subject: [PATCH 109/765] gmst verifier --- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/doc/document.cpp | 2008 +------------------ apps/opencs/model/tools/gmstcheck.cpp | 91 + apps/opencs/model/tools/gmstcheck.hpp | 32 + apps/opencs/model/tools/tools.cpp | 3 + apps/opencs/model/world/defaultgmsts.cpp | 2336 ++++++++++++++++++++++ apps/opencs/model/world/defaultgmsts.hpp | 34 + 7 files changed, 2514 insertions(+), 1994 deletions(-) create mode 100644 apps/opencs/model/tools/gmstcheck.cpp create mode 100644 apps/opencs/model/tools/gmstcheck.hpp create mode 100644 apps/opencs/model/world/defaultgmsts.cpp create mode 100644 apps/opencs/model/world/defaultgmsts.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 8bb624704b..0d621c83e6 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata + idcompletionmanager metadata defaultgmsts ) opencs_hdrs_noqt (model/world @@ -42,7 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages + mergestages gmstcheck ) opencs_hdrs_noqt (model/tools diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 80c14bd985..ea1fbb3305 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -6,1925 +6,36 @@ #include +#include "../world/defaultgmsts.hpp" + #ifndef Q_MOC_RUN #include #endif void CSMDoc::Document::addGmsts() { - static const char *gmstFloats[] = - { - "fAIFleeFleeMult", - "fAIFleeHealthMult", - "fAIMagicSpellMult", - "fAIMeleeArmorMult", - "fAIMeleeSummWeaponMult", - "fAIMeleeWeaponMult", - "fAIRangeMagicSpellMult", - "fAIRangeMeleeWeaponMult", - "fAlarmRadius", - "fAthleticsRunBonus", - "fAudioDefaultMaxDistance", - "fAudioDefaultMinDistance", - "fAudioMaxDistanceMult", - "fAudioMinDistanceMult", - "fAudioVoiceDefaultMaxDistance", - "fAudioVoiceDefaultMinDistance", - "fAutoPCSpellChance", - "fAutoSpellChance", - "fBargainOfferBase", - "fBargainOfferMulti", - "fBarterGoldResetDelay", - "fBaseRunMultiplier", - "fBlockStillBonus", - "fBribe1000Mod", - "fBribe100Mod", - "fBribe10Mod", - "fCombatAngleXY", - "fCombatAngleZ", - "fCombatArmorMinMult", - "fCombatBlockLeftAngle", - "fCombatBlockRightAngle", - "fCombatCriticalStrikeMult", - "fCombatDelayCreature", - "fCombatDelayNPC", - "fCombatDistance", - "fCombatDistanceWerewolfMod", - "fCombatForceSideAngle", - "fCombatInvisoMult", - "fCombatKODamageMult", - "fCombatTorsoSideAngle", - "fCombatTorsoStartPercent", - "fCombatTorsoStopPercent", - "fConstantEffectMult", - "fCorpseClearDelay", - "fCorpseRespawnDelay", - "fCrimeGoldDiscountMult", - "fCrimeGoldTurnInMult", - "fCrimeStealing", - "fDamageStrengthBase", - "fDamageStrengthMult", - "fDifficultyMult", - "fDiseaseXferChance", - "fDispAttacking", - "fDispBargainFailMod", - "fDispBargainSuccessMod", - "fDispCrimeMod", - "fDispDiseaseMod", - "fDispFactionMod", - "fDispFactionRankBase", - "fDispFactionRankMult", - "fDispositionMod", - "fDispPersonalityBase", - "fDispPersonalityMult", - "fDispPickPocketMod", - "fDispRaceMod", - "fDispStealing", - "fDispWeaponDrawn", - "fEffectCostMult", - "fElementalShieldMult", - "fEnchantmentChanceMult", - "fEnchantmentConstantChanceMult", - "fEnchantmentConstantDurationMult", - "fEnchantmentMult", - "fEnchantmentValueMult", - "fEncumberedMoveEffect", - "fEncumbranceStrMult", - "fEndFatigueMult", - "fFallAcroBase", - "fFallAcroMult", - "fFallDamageDistanceMin", - "fFallDistanceBase", - "fFallDistanceMult", - "fFatigueAttackBase", - "fFatigueAttackMult", - "fFatigueBase", - "fFatigueBlockBase", - "fFatigueBlockMult", - "fFatigueJumpBase", - "fFatigueJumpMult", - "fFatigueMult", - "fFatigueReturnBase", - "fFatigueReturnMult", - "fFatigueRunBase", - "fFatigueRunMult", - "fFatigueSneakBase", - "fFatigueSneakMult", - "fFatigueSpellBase", - "fFatigueSpellCostMult", - "fFatigueSpellMult", - "fFatigueSwimRunBase", - "fFatigueSwimRunMult", - "fFatigueSwimWalkBase", - "fFatigueSwimWalkMult", - "fFightDispMult", - "fFightDistanceMultiplier", - "fFightStealing", - "fFleeDistance", - "fGreetDistanceReset", - "fHandtoHandHealthPer", - "fHandToHandReach", - "fHoldBreathEndMult", - "fHoldBreathTime", - "fIdleChanceMultiplier", - "fIngredientMult", - "fInteriorHeadTrackMult", - "fJumpAcrobaticsBase", - "fJumpAcroMultiplier", - "fJumpEncumbranceBase", - "fJumpEncumbranceMultiplier", - "fJumpMoveBase", - "fJumpMoveMult", - "fJumpRunMultiplier", - "fKnockDownMult", - "fLevelMod", - "fLevelUpHealthEndMult", - "fLightMaxMod", - "fLuckMod", - "fMagesGuildTravel", - "fMagicCreatureCastDelay", - "fMagicDetectRefreshRate", - "fMagicItemConstantMult", - "fMagicItemCostMult", - "fMagicItemOnceMult", - "fMagicItemPriceMult", - "fMagicItemRechargePerSecond", - "fMagicItemStrikeMult", - "fMagicItemUsedMult", - "fMagicStartIconBlink", - "fMagicSunBlockedMult", - "fMajorSkillBonus", - "fMaxFlySpeed", - "fMaxHandToHandMult", - "fMaxHeadTrackDistance", - "fMaxWalkSpeed", - "fMaxWalkSpeedCreature", - "fMedMaxMod", - "fMessageTimePerChar", - "fMinFlySpeed", - "fMinHandToHandMult", - "fMinorSkillBonus", - "fMinWalkSpeed", - "fMinWalkSpeedCreature", - "fMiscSkillBonus", - "fNPCbaseMagickaMult", - "fNPCHealthBarFade", - "fNPCHealthBarTime", - "fPCbaseMagickaMult", - "fPerDieRollMult", - "fPersonalityMod", - "fPerTempMult", - "fPickLockMult", - "fPickPocketMod", - "fPotionMinUsefulDuration", - "fPotionStrengthMult", - "fPotionT1DurMult", - "fPotionT1MagMult", - "fPotionT4BaseStrengthMult", - "fPotionT4EquipStrengthMult", - "fProjectileMaxSpeed", - "fProjectileMinSpeed", - "fProjectileThrownStoreChance", - "fRepairAmountMult", - "fRepairMult", - "fReputationMod", - "fRestMagicMult", - "fSeriousWoundMult", - "fSleepRandMod", - "fSleepRestMod", - "fSneakBootMult", - "fSneakDistanceBase", - "fSneakDistanceMultiplier", - "fSneakNoViewMult", - "fSneakSkillMult", - "fSneakSpeedMultiplier", - "fSneakUseDelay", - "fSneakUseDist", - "fSneakViewMult", - "fSoulGemMult", - "fSpecialSkillBonus", - "fSpellMakingValueMult", - "fSpellPriceMult", - "fSpellValueMult", - "fStromWalkMult", - "fStromWindSpeed", - "fSuffocationDamage", - "fSwimHeightScale", - "fSwimRunAthleticsMult", - "fSwimRunBase", - "fSwimWalkAthleticsMult", - "fSwimWalkBase", - "fSwingBlockBase", - "fSwingBlockMult", - "fTargetSpellMaxSpeed", - "fThrownWeaponMaxSpeed", - "fThrownWeaponMinSpeed", - "fTrapCostMult", - "fTravelMult", - "fTravelTimeMult", - "fUnarmoredBase1", - "fUnarmoredBase2", - "fVanityDelay", - "fVoiceIdleOdds", - "fWaterReflectUpdateAlways", - "fWaterReflectUpdateSeldom", - "fWeaponDamageMult", - "fWeaponFatigueBlockMult", - "fWeaponFatigueMult", - "fWereWolfAcrobatics", - "fWereWolfAgility", - "fWereWolfAlchemy", - "fWereWolfAlteration", - "fWereWolfArmorer", - "fWereWolfAthletics", - "fWereWolfAxe", - "fWereWolfBlock", - "fWereWolfBluntWeapon", - "fWereWolfConjuration", - "fWereWolfDestruction", - "fWereWolfEnchant", - "fWereWolfEndurance", - "fWereWolfFatigue", - "fWereWolfHandtoHand", - "fWereWolfHealth", - "fWereWolfHeavyArmor", - "fWereWolfIllusion", - "fWereWolfIntellegence", - "fWereWolfLightArmor", - "fWereWolfLongBlade", - "fWereWolfLuck", - "fWereWolfMagicka", - "fWereWolfMarksman", - "fWereWolfMediumArmor", - "fWereWolfMerchantile", - "fWereWolfMysticism", - "fWereWolfPersonality", - "fWereWolfRestoration", - "fWereWolfRunMult", - "fWereWolfSecurity", - "fWereWolfShortBlade", - "fWereWolfSilverWeaponDamageMult", - "fWereWolfSneak", - "fWereWolfSpear", - "fWereWolfSpeechcraft", - "fWereWolfSpeed", - "fWereWolfStrength", - "fWereWolfUnarmored", - "fWereWolfWillPower", - "fWortChanceValue", - 0 - }; - - static const float gmstFloatsValues[] = - { - 0.3, // fAIFleeFleeMult - 7.0, // fAIFleeHealthMult - 3.0, // fAIMagicSpellMult - 1.0, // fAIMeleeArmorMult - 1.0, // fAIMeleeSummWeaponMult - 2.0, // fAIMeleeWeaponMult - 5.0, // fAIRangeMagicSpellMult - 5.0, // fAIRangeMeleeWeaponMult - 2000.0, // fAlarmRadius - 1.0, // fAthleticsRunBonus - 40.0, // fAudioDefaultMaxDistance - 5.0, // fAudioDefaultMinDistance - 50.0, // fAudioMaxDistanceMult - 20.0, // fAudioMinDistanceMult - 60.0, // fAudioVoiceDefaultMaxDistance - 10.0, // fAudioVoiceDefaultMinDistance - 50.0, // fAutoPCSpellChance - 80.0, // fAutoSpellChance - 50.0, // fBargainOfferBase - -4.0, // fBargainOfferMulti - 24.0, // fBarterGoldResetDelay - 1.75, // fBaseRunMultiplier - 1.25, // fBlockStillBonus - 150.0, // fBribe1000Mod - 75.0, // fBribe100Mod - 35.0, // fBribe10Mod - 60.0, // fCombatAngleXY - 60.0, // fCombatAngleZ - 0.25, // fCombatArmorMinMult - -90.0, // fCombatBlockLeftAngle - 30.0, // fCombatBlockRightAngle - 4.0, // fCombatCriticalStrikeMult - 0.1, // fCombatDelayCreature - 0.1, // fCombatDelayNPC - 128.0, // fCombatDistance - 0.3, // fCombatDistanceWerewolfMod - 30.0, // fCombatForceSideAngle - 0.2, // fCombatInvisoMult - 1.5, // fCombatKODamageMult - 45.0, // fCombatTorsoSideAngle - 0.3, // fCombatTorsoStartPercent - 0.8, // fCombatTorsoStopPercent - 15.0, // fConstantEffectMult - 72.0, // fCorpseClearDelay - 72.0, // fCorpseRespawnDelay - 0.5, // fCrimeGoldDiscountMult - 0.9, // fCrimeGoldTurnInMult - 1.0, // fCrimeStealing - 0.5, // fDamageStrengthBase - 0.1, // fDamageStrengthMult - 5.0, // fDifficultyMult - 2.5, // fDiseaseXferChance - -10.0, // fDispAttacking - -1.0, // fDispBargainFailMod - 1.0, // fDispBargainSuccessMod - 0.0, // fDispCrimeMod - -10.0, // fDispDiseaseMod - 3.0, // fDispFactionMod - 1.0, // fDispFactionRankBase - 0.5, // fDispFactionRankMult - 1.0, // fDispositionMod - 50.0, // fDispPersonalityBase - 0.5, // fDispPersonalityMult - -25.0, // fDispPickPocketMod - 5.0, // fDispRaceMod - -0.5, // fDispStealing - -5.0, // fDispWeaponDrawn - 0.5, // fEffectCostMult - 0.1, // fElementalShieldMult - 3.0, // fEnchantmentChanceMult - 0.5, // fEnchantmentConstantChanceMult - 100.0, // fEnchantmentConstantDurationMult - 0.1, // fEnchantmentMult - 1000.0, // fEnchantmentValueMult - 0.3, // fEncumberedMoveEffect - 5.0, // fEncumbranceStrMult - 0.04, // fEndFatigueMult - 0.25, // fFallAcroBase - 0.01, // fFallAcroMult - 400.0, // fFallDamageDistanceMin - 0.0, // fFallDistanceBase - 0.07, // fFallDistanceMult - 2.0, // fFatigueAttackBase - 0.0, // fFatigueAttackMult - 1.25, // fFatigueBase - 4.0, // fFatigueBlockBase - 0.0, // fFatigueBlockMult - 5.0, // fFatigueJumpBase - 0.0, // fFatigueJumpMult - 0.5, // fFatigueMult - 2.5, // fFatigueReturnBase - 0.02, // fFatigueReturnMult - 5.0, // fFatigueRunBase - 2.0, // fFatigueRunMult - 1.5, // fFatigueSneakBase - 1.5, // fFatigueSneakMult - 0.0, // fFatigueSpellBase - 0.0, // fFatigueSpellCostMult - 0.0, // fFatigueSpellMult - 7.0, // fFatigueSwimRunBase - 0.0, // fFatigueSwimRunMult - 2.5, // fFatigueSwimWalkBase - 0.0, // fFatigueSwimWalkMult - 0.2, // fFightDispMult - 0.005, // fFightDistanceMultiplier - 50.0, // fFightStealing - 3000.0, // fFleeDistance - 512.0, // fGreetDistanceReset - 0.1, // fHandtoHandHealthPer - 1.0, // fHandToHandReach - 0.5, // fHoldBreathEndMult - 20.0, // fHoldBreathTime - 0.75, // fIdleChanceMultiplier - 1.0, // fIngredientMult - 0.5, // fInteriorHeadTrackMult - 128.0, // fJumpAcrobaticsBase - 4.0, // fJumpAcroMultiplier - 0.5, // fJumpEncumbranceBase - 1.0, // fJumpEncumbranceMultiplier - 0.5, // fJumpMoveBase - 0.5, // fJumpMoveMult - 1.0, // fJumpRunMultiplier - 0.5, // fKnockDownMult - 5.0, // fLevelMod - 0.1, // fLevelUpHealthEndMult - 0.6, // fLightMaxMod - 10.0, // fLuckMod - 10.0, // fMagesGuildTravel - 1.5, // fMagicCreatureCastDelay - 0.0167, // fMagicDetectRefreshRate - 1.0, // fMagicItemConstantMult - 1.0, // fMagicItemCostMult - 1.0, // fMagicItemOnceMult - 1.0, // fMagicItemPriceMult - 0.05, // fMagicItemRechargePerSecond - 1.0, // fMagicItemStrikeMult - 1.0, // fMagicItemUsedMult - 3.0, // fMagicStartIconBlink - 0.5, // fMagicSunBlockedMult - 0.75, // fMajorSkillBonus - 300.0, // fMaxFlySpeed - 0.5, // fMaxHandToHandMult - 400.0, // fMaxHeadTrackDistance - 200.0, // fMaxWalkSpeed - 300.0, // fMaxWalkSpeedCreature - 0.9, // fMedMaxMod - 0.1, // fMessageTimePerChar - 5.0, // fMinFlySpeed - 0.1, // fMinHandToHandMult - 1.0, // fMinorSkillBonus - 100.0, // fMinWalkSpeed - 5.0, // fMinWalkSpeedCreature - 1.25, // fMiscSkillBonus - 2.0, // fNPCbaseMagickaMult - 0.5, // fNPCHealthBarFade - 3.0, // fNPCHealthBarTime - 1.0, // fPCbaseMagickaMult - 0.3, // fPerDieRollMult - 5.0, // fPersonalityMod - 1.0, // fPerTempMult - -1.0, // fPickLockMult - 0.3, // fPickPocketMod - 20.0, // fPotionMinUsefulDuration - 0.5, // fPotionStrengthMult - 0.5, // fPotionT1DurMult - 1.5, // fPotionT1MagMult - 20.0, // fPotionT4BaseStrengthMult - 12.0, // fPotionT4EquipStrengthMult - 3000.0, // fProjectileMaxSpeed - 400.0, // fProjectileMinSpeed - 25.0, // fProjectileThrownStoreChance - 3.0, // fRepairAmountMult - 1.0, // fRepairMult - 1.0, // fReputationMod - 0.15, // fRestMagicMult - 0.0, // fSeriousWoundMult - 0.25, // fSleepRandMod - 0.3, // fSleepRestMod - -1.0, // fSneakBootMult - 0.5, // fSneakDistanceBase - 0.002, // fSneakDistanceMultiplier - 0.5, // fSneakNoViewMult - 1.0, // fSneakSkillMult - 0.75, // fSneakSpeedMultiplier - 1.0, // fSneakUseDelay - 500.0, // fSneakUseDist - 1.5, // fSneakViewMult - 3.0, // fSoulGemMult - 0.8, // fSpecialSkillBonus - 7.0, // fSpellMakingValueMult - 2.0, // fSpellPriceMult - 10.0, // fSpellValueMult - 0.25, // fStromWalkMult - 0.7, // fStromWindSpeed - 3.0, // fSuffocationDamage - 0.9, // fSwimHeightScale - 0.1, // fSwimRunAthleticsMult - 0.5, // fSwimRunBase - 0.02, // fSwimWalkAthleticsMult - 0.5, // fSwimWalkBase - 1.0, // fSwingBlockBase - 1.0, // fSwingBlockMult - 1000.0, // fTargetSpellMaxSpeed - 1000.0, // fThrownWeaponMaxSpeed - 300.0, // fThrownWeaponMinSpeed - 0.0, // fTrapCostMult - 4000.0, // fTravelMult - 16000.0,// fTravelTimeMult - 0.1, // fUnarmoredBase1 - 0.065, // fUnarmoredBase2 - 30.0, // fVanityDelay - 10.0, // fVoiceIdleOdds - 0.0, // fWaterReflectUpdateAlways - 10.0, // fWaterReflectUpdateSeldom - 0.1, // fWeaponDamageMult - 1.0, // fWeaponFatigueBlockMult - 0.25, // fWeaponFatigueMult - 150.0, // fWereWolfAcrobatics - 150.0, // fWereWolfAgility - 1.0, // fWereWolfAlchemy - 1.0, // fWereWolfAlteration - 1.0, // fWereWolfArmorer - 150.0, // fWereWolfAthletics - 1.0, // fWereWolfAxe - 1.0, // fWereWolfBlock - 1.0, // fWereWolfBluntWeapon - 1.0, // fWereWolfConjuration - 1.0, // fWereWolfDestruction - 1.0, // fWereWolfEnchant - 150.0, // fWereWolfEndurance - 400.0, // fWereWolfFatigue - 100.0, // fWereWolfHandtoHand - 2.0, // fWereWolfHealth - 1.0, // fWereWolfHeavyArmor - 1.0, // fWereWolfIllusion - 1.0, // fWereWolfIntellegence - 1.0, // fWereWolfLightArmor - 1.0, // fWereWolfLongBlade - 1.0, // fWereWolfLuck - 100.0, // fWereWolfMagicka - 1.0, // fWereWolfMarksman - 1.0, // fWereWolfMediumArmor - 1.0, // fWereWolfMerchantile - 1.0, // fWereWolfMysticism - 1.0, // fWereWolfPersonality - 1.0, // fWereWolfRestoration - 1.5, // fWereWolfRunMult - 1.0, // fWereWolfSecurity - 1.0, // fWereWolfShortBlade - 1.5, // fWereWolfSilverWeaponDamageMult - 1.0, // fWereWolfSneak - 1.0, // fWereWolfSpear - 1.0, // fWereWolfSpeechcraft - 150.0, // fWereWolfSpeed - 150.0, // fWereWolfStrength - 100.0, // fWereWolfUnarmored - 1.0, // fWereWolfWillPower - 15.0, // fWortChanceValue - }; - - static const char *gmstIntegers[] = - { - "i1stPersonSneakDelta", - "iAlarmAttack", - "iAlarmKilling", - "iAlarmPickPocket", - "iAlarmStealing", - "iAlarmTresspass", - "iAlchemyMod", - "iAutoPCSpellMax", - "iAutoRepFacMod", - "iAutoRepLevMod", - "iAutoSpellAlterationMax", - "iAutoSpellAttSkillMin", - "iAutoSpellConjurationMax", - "iAutoSpellDestructionMax", - "iAutoSpellIllusionMax", - "iAutoSpellMysticismMax", - "iAutoSpellRestorationMax", - "iAutoSpellTimesCanCast", - "iBarterFailDisposition", - "iBarterSuccessDisposition", - "iBaseArmorSkill", - "iBlockMaxChance", - "iBlockMinChance", - "iBootsWeight", - "iCrimeAttack", - "iCrimeKilling", - "iCrimePickPocket", - "iCrimeThreshold", - "iCrimeThresholdMultiplier", - "iCrimeTresspass", - "iCuirassWeight", - "iDaysinPrisonMod", - "iDispAttackMod", - "iDispKilling", - "iDispTresspass", - "iFightAlarmMult", - "iFightAttack", - "iFightAttacking", - "iFightDistanceBase", - "iFightKilling", - "iFightPickpocket", - "iFightTrespass", - "iFlee", - "iGauntletWeight", - "iGreavesWeight", - "iGreetDistanceMultiplier", - "iGreetDuration", - "iHelmWeight", - "iKnockDownOddsBase", - "iKnockDownOddsMult", - "iLevelUp01Mult", - "iLevelUp02Mult", - "iLevelUp03Mult", - "iLevelUp04Mult", - "iLevelUp05Mult", - "iLevelUp06Mult", - "iLevelUp07Mult", - "iLevelUp08Mult", - "iLevelUp09Mult", - "iLevelUp10Mult", - "iLevelupMajorMult", - "iLevelupMajorMultAttribute", - "iLevelupMinorMult", - "iLevelupMinorMultAttribute", - "iLevelupMiscMultAttriubte", - "iLevelupSpecialization", - "iLevelupTotal", - "iMagicItemChargeConst", - "iMagicItemChargeOnce", - "iMagicItemChargeStrike", - "iMagicItemChargeUse", - "iMaxActivateDist", - "iMaxInfoDist", - "iMonthsToRespawn", - "iNumberCreatures", - "iPauldronWeight", - "iPerMinChance", - "iPerMinChange", - "iPickMaxChance", - "iPickMinChance", - "iShieldWeight", - "iSoulAmountForConstantEffect", - "iTrainingMod", - "iVoiceAttackOdds", - "iVoiceHitOdds", - "iWereWolfBounty", - "iWereWolfFightMod", - "iWereWolfFleeMod", - "iWereWolfLevelToAttack", - 0 - }; - - static const int gmstIntegersValues[] = - { - 10, // i1stPersonSneakDelta - 50, // iAlarmAttack - 90, // iAlarmKilling - 20, // iAlarmPickPocket - 1, // iAlarmStealing - 5, // iAlarmTresspass - 2, // iAlchemyMod - 100, // iAutoPCSpellMax - 2, // iAutoRepFacMod - 0, // iAutoRepLevMod - 5, // iAutoSpellAlterationMax - 70, // iAutoSpellAttSkillMin - 2, // iAutoSpellConjurationMax - 5, // iAutoSpellDestructionMax - 5, // iAutoSpellIllusionMax - 5, // iAutoSpellMysticismMax - 5, // iAutoSpellRestorationMax - 3, // iAutoSpellTimesCanCast - -1, // iBarterFailDisposition - 1, // iBarterSuccessDisposition - 30, // iBaseArmorSkill - 50, // iBlockMaxChance - 10, // iBlockMinChance - 20, // iBootsWeight - 40, // iCrimeAttack - 1000, // iCrimeKilling - 25, // iCrimePickPocket - 1000, // iCrimeThreshold - 10, // iCrimeThresholdMultiplier - 5, // iCrimeTresspass - 30, // iCuirassWeight - 100, // iDaysinPrisonMod - -50, // iDispAttackMod - -50, // iDispKilling - -20, // iDispTresspass - 1, // iFightAlarmMult - 100, // iFightAttack - 50, // iFightAttacking - 20, // iFightDistanceBase - 50, // iFightKilling - 25, // iFightPickpocket - 25, // iFightTrespass - 0, // iFlee - 5, // iGauntletWeight - 15, // iGreavesWeight - 6, // iGreetDistanceMultiplier - 4, // iGreetDuration - 5, // iHelmWeight - 50, // iKnockDownOddsBase - 50, // iKnockDownOddsMult - 2, // iLevelUp01Mult - 2, // iLevelUp02Mult - 2, // iLevelUp03Mult - 2, // iLevelUp04Mult - 3, // iLevelUp05Mult - 3, // iLevelUp06Mult - 3, // iLevelUp07Mult - 4, // iLevelUp08Mult - 4, // iLevelUp09Mult - 5, // iLevelUp10Mult - 1, // iLevelupMajorMult - 1, // iLevelupMajorMultAttribute - 1, // iLevelupMinorMult - 1, // iLevelupMinorMultAttribute - 1, // iLevelupMiscMultAttriubte - 1, // iLevelupSpecialization - 10, // iLevelupTotal - 10, // iMagicItemChargeConst - 1, // iMagicItemChargeOnce - 10, // iMagicItemChargeStrike - 5, // iMagicItemChargeUse - 192, // iMaxActivateDist - 192, // iMaxInfoDist - 4, // iMonthsToRespawn - 1, // iNumberCreatures - 10, // iPauldronWeight - 5, // iPerMinChance - 10, // iPerMinChange - 75, // iPickMaxChance - 5, // iPickMinChance - 15, // iShieldWeight - 400, // iSoulAmountForConstantEffect - 10, // iTrainingMod - 10, // iVoiceAttackOdds - 30, // iVoiceHitOdds - 10000, // iWereWolfBounty - 100, // iWereWolfFightMod - 100, // iWereWolfFleeMod - 20, // iWereWolfLevelToAttack - }; - - static const char *gmstStrings[] = - { - "s3dAudio", - "s3dHardware", - "s3dSoftware", - "sAbsorb", - "sAcrobat", - "sActivate", - "sActivateXbox", - "sActorInCombat", - "sAdmire", - "sAdmireFail", - "sAdmireSuccess", - "sAgent", - "sAgiDesc", - "sAIDistance", - "sAlembic", - "sAllTab", - "sAlways", - "sAlways_Run", - "sand", - "sApparatus", - "sApparelTab", - "sArcher", - "sArea", - "sAreaDes", - "sArmor", - "sArmorRating", - "sAsk", - "sAssassin", - "sAt", - "sAttack", - "sAttributeAgility", - "sAttributeEndurance", - "sAttributeIntelligence", - "sAttributeListTitle", - "sAttributeLuck", - "sAttributePersonality", - "sAttributesMenu1", - "sAttributeSpeed", - "sAttributeStrength", - "sAttributeWillpower", - "sAudio", - "sAuto_Run", - "sBack", - "sBackspace", - "sBackXbox", - "sBarbarian", - "sBard", - "sBarter", - "sBarterDialog1", - "sBarterDialog10", - "sBarterDialog11", - "sBarterDialog12", - "sBarterDialog2", - "sBarterDialog3", - "sBarterDialog4", - "sBarterDialog5", - "sBarterDialog6", - "sBarterDialog7", - "sBarterDialog8", - "sBarterDialog9", - "sBattlemage", - "sBestAttack", - "sBirthSign", - "sBirthsignmenu1", - "sBirthsignmenu2", - "sBlocks", - "sBonusSkillTitle", - "sBookPageOne", - "sBookPageTwo", - "sBookSkillMessage", - "sBounty", - "sBreath", - "sBribe 10 Gold", - "sBribe 100 Gold", - "sBribe 1000 Gold", - "sBribeFail", - "sBribeSuccess", - "sBuy", - "sBye", - "sCalcinator", - "sCancel", - "sCantEquipWeapWarning", - "sCastCost", - "sCaughtStealingMessage", - "sCenter", - "sChangedMastersMsg", - "sCharges", - "sChooseClassMenu1", - "sChooseClassMenu2", - "sChooseClassMenu3", - "sChooseClassMenu4", - "sChop", - "sClass", - "sClassChoiceMenu1", - "sClassChoiceMenu2", - "sClassChoiceMenu3", - "sClose", - "sCompanionShare", - "sCompanionWarningButtonOne", - "sCompanionWarningButtonTwo", - "sCompanionWarningMessage", - "sCondition", - "sConsoleTitle", - "sContainer", - "sContentsMessage1", - "sContentsMessage2", - "sContentsMessage3", - "sControlerVibration", - "sControls", - "sControlsMenu1", - "sControlsMenu2", - "sControlsMenu3", - "sControlsMenu4", - "sControlsMenu5", - "sControlsMenu6", - "sCostChance", - "sCostCharge", - "sCreate", - "sCreateClassMenu1", - "sCreateClassMenu2", - "sCreateClassMenu3", - "sCreateClassMenuHelp1", - "sCreateClassMenuHelp2", - "sCreateClassMenuWarning", - "sCreatedEffects", - "sCrimeHelp", - "sCrimeMessage", - "sCrouch_Sneak", - "sCrouchXbox", - "sCrusader", - "sCursorOff", - "sCustom", - "sCustomClassName", - "sDamage", - "sDark_Gamma", - "sDay", - "sDefaultCellname", - "sDelete", - "sDeleteGame", - "sDeleteNote", - "sDeleteSpell", - "sDeleteSpellError", - "sDetail_Level", - "sDialogMenu1", - "sDialogText1Xbox", - "sDialogText2Xbox", - "sDialogText3Xbox", - "sDifficulty", - "sDisposeCorpseFail", - "sDisposeofCorpse", - "sDone", - "sDoYouWantTo", - "sDrain", - "sDrop", - "sDuration", - "sDurationDes", - "sEasy", - "sEditNote", - "sEffectAbsorbAttribute", - "sEffectAbsorbFatigue", - "sEffectAbsorbHealth", - "sEffectAbsorbSkill", - "sEffectAbsorbSpellPoints", - "sEffectAlmsiviIntervention", - "sEffectBlind", - "sEffectBoundBattleAxe", - "sEffectBoundBoots", - "sEffectBoundCuirass", - "sEffectBoundDagger", - "sEffectBoundGloves", - "sEffectBoundHelm", - "sEffectBoundLongbow", - "sEffectBoundLongsword", - "sEffectBoundMace", - "sEffectBoundShield", - "sEffectBoundSpear", - "sEffectBurden", - "sEffectCalmCreature", - "sEffectCalmHumanoid", - "sEffectChameleon", - "sEffectCharm", - "sEffectCommandCreatures", - "sEffectCommandHumanoids", - "sEffectCorpus", - "sEffectCureBlightDisease", - "sEffectCureCommonDisease", - "sEffectCureCorprusDisease", - "sEffectCureParalyzation", - "sEffectCurePoison", - "sEffectDamageAttribute", - "sEffectDamageFatigue", - "sEffectDamageHealth", - "sEffectDamageMagicka", - "sEffectDamageSkill", - "sEffectDemoralizeCreature", - "sEffectDemoralizeHumanoid", - "sEffectDetectAnimal", - "sEffectDetectEnchantment", - "sEffectDetectKey", - "sEffectDisintegrateArmor", - "sEffectDisintegrateWeapon", - "sEffectDispel", - "sEffectDivineIntervention", - "sEffectDrainAttribute", - "sEffectDrainFatigue", - "sEffectDrainHealth", - "sEffectDrainSkill", - "sEffectDrainSpellpoints", - "sEffectExtraSpell", - "sEffectFeather", - "sEffectFireDamage", - "sEffectFireShield", - "sEffectFortifyAttackBonus", - "sEffectFortifyAttribute", - "sEffectFortifyFatigue", - "sEffectFortifyHealth", - "sEffectFortifyMagickaMultiplier", - "sEffectFortifySkill", - "sEffectFortifySpellpoints", - "sEffectFrenzyCreature", - "sEffectFrenzyHumanoid", - "sEffectFrostDamage", - "sEffectFrostShield", - "sEffectInvisibility", - "sEffectJump", - "sEffectLevitate", - "sEffectLight", - "sEffectLightningShield", - "sEffectLock", - "sEffectMark", - "sEffectNightEye", - "sEffectOpen", - "sEffectParalyze", - "sEffectPoison", - "sEffectRallyCreature", - "sEffectRallyHumanoid", - "sEffectRecall", - "sEffectReflect", - "sEffectRemoveCurse", - "sEffectResistBlightDisease", - "sEffectResistCommonDisease", - "sEffectResistCorprusDisease", - "sEffectResistFire", - "sEffectResistFrost", - "sEffectResistMagicka", - "sEffectResistNormalWeapons", - "sEffectResistParalysis", - "sEffectResistPoison", - "sEffectResistShock", - "sEffectRestoreAttribute", - "sEffectRestoreFatigue", - "sEffectRestoreHealth", - "sEffectRestoreSkill", - "sEffectRestoreSpellPoints", - "sEffects", - "sEffectSanctuary", - "sEffectShield", - "sEffectShockDamage", - "sEffectSilence", - "sEffectSlowFall", - "sEffectSoultrap", - "sEffectSound", - "sEffectSpellAbsorption", - "sEffectStuntedMagicka", - "sEffectSummonAncestralGhost", - "sEffectSummonBonelord", - "sEffectSummonCenturionSphere", - "sEffectSummonClannfear", - "sEffectSummonCreature01", - "sEffectSummonCreature02", - "sEffectSummonCreature03", - "sEffectSummonCreature04", - "sEffectSummonCreature05", - "sEffectSummonDaedroth", - "sEffectSummonDremora", - "sEffectSummonFabricant", - "sEffectSummonFlameAtronach", - "sEffectSummonFrostAtronach", - "sEffectSummonGoldensaint", - "sEffectSummonGreaterBonewalker", - "sEffectSummonHunger", - "sEffectSummonLeastBonewalker", - "sEffectSummonScamp", - "sEffectSummonSkeletalMinion", - "sEffectSummonStormAtronach", - "sEffectSummonWingedTwilight", - "sEffectSunDamage", - "sEffectSwiftSwim", - "sEffectTelekinesis", - "sEffectTurnUndead", - "sEffectVampirism", - "sEffectWaterBreathing", - "sEffectWaterWalking", - "sEffectWeaknessToBlightDisease", - "sEffectWeaknessToCommonDisease", - "sEffectWeaknessToCorprusDisease", - "sEffectWeaknessToFire", - "sEffectWeaknessToFrost", - "sEffectWeaknessToMagicka", - "sEffectWeaknessToNormalWeapons", - "sEffectWeaknessToPoison", - "sEffectWeaknessToShock", - "sEnableJoystick", - "sEnchanting", - "sEnchantItems", - "sEnchantmentHelp1", - "sEnchantmentHelp10", - "sEnchantmentHelp2", - "sEnchantmentHelp3", - "sEnchantmentHelp4", - "sEnchantmentHelp5", - "sEnchantmentHelp6", - "sEnchantmentHelp7", - "sEnchantmentHelp8", - "sEnchantmentHelp9", - "sEnchantmentMenu1", - "sEnchantmentMenu10", - "sEnchantmentMenu11", - "sEnchantmentMenu12", - "sEnchantmentMenu2", - "sEnchantmentMenu3", - "sEnchantmentMenu4", - "sEnchantmentMenu5", - "sEnchantmentMenu6", - "sEnchantmentMenu7", - "sEnchantmentMenu8", - "sEnchantmentMenu9", - "sEncumbrance", - "sEndDesc", - "sEquip", - "sExitGame", - "sExpelled", - "sExpelledMessage", - "sFace", - "sFaction", - "sFar", - "sFast", - "sFatDesc", - "sFatigue", - "sFavoriteSkills", - "sfeet", - "sFileSize", - "sfootarea", - "sFootsteps", - "sfor", - "sFortify", - "sForward", - "sForwardXbox", - "sFull", - "sGame", - "sGameWithoutLauncherXbox", - "sGamma_Correction", - "sGeneralMastPlugMismatchMsg", - "sGold", - "sGoodbye", - "sGoverningAttribute", - "sgp", - "sHair", - "sHard", - "sHeal", - "sHealer", - "sHealth", - "sHealthDesc", - "sHealthPerHourOfRest", - "sHealthPerLevel", - "sHeavy", - "sHigh", - "sin", - "sInfo", - "sInfoRefusal", - "sIngredients", - "sInPrisonTitle", - "sInputMenu1", - "sIntDesc", - "sIntimidate", - "sIntimidateFail", - "sIntimidateSuccess", - "sInvalidSaveGameMsg", - "sInvalidSaveGameMsgXBOX", - "sInventory", - "sInventoryMenu1", - "sInventoryMessage1", - "sInventoryMessage2", - "sInventoryMessage3", - "sInventoryMessage4", - "sInventoryMessage5", - "sInventorySelectNoIngredients", - "sInventorySelectNoItems", - "sInventorySelectNoSoul", - "sItem", - "sItemCastConstant", - "sItemCastOnce", - "sItemCastWhenStrikes", - "sItemCastWhenUsed", - "sItemName", - "sJournal", - "sJournalCmd", - "sJournalEntry", - "sJournalXbox", - "sJoystickHatShort", - "sJoystickNotFound", - "sJoystickShort", - "sJump", - "sJumpXbox", - "sKeyName_00", - "sKeyName_01", - "sKeyName_02", - "sKeyName_03", - "sKeyName_04", - "sKeyName_05", - "sKeyName_06", - "sKeyName_07", - "sKeyName_08", - "sKeyName_09", - "sKeyName_0A", - "sKeyName_0B", - "sKeyName_0C", - "sKeyName_0D", - "sKeyName_0E", - "sKeyName_0F", - "sKeyName_10", - "sKeyName_11", - "sKeyName_12", - "sKeyName_13", - "sKeyName_14", - "sKeyName_15", - "sKeyName_16", - "sKeyName_17", - "sKeyName_18", - "sKeyName_19", - "sKeyName_1A", - "sKeyName_1B", - "sKeyName_1C", - "sKeyName_1D", - "sKeyName_1E", - "sKeyName_1F", - "sKeyName_20", - "sKeyName_21", - "sKeyName_22", - "sKeyName_23", - "sKeyName_24", - "sKeyName_25", - "sKeyName_26", - "sKeyName_27", - "sKeyName_28", - "sKeyName_29", - "sKeyName_2A", - "sKeyName_2B", - "sKeyName_2C", - "sKeyName_2D", - "sKeyName_2E", - "sKeyName_2F", - "sKeyName_30", - "sKeyName_31", - "sKeyName_32", - "sKeyName_33", - "sKeyName_34", - "sKeyName_35", - "sKeyName_36", - "sKeyName_37", - "sKeyName_38", - "sKeyName_39", - "sKeyName_3A", - "sKeyName_3B", - "sKeyName_3C", - "sKeyName_3D", - "sKeyName_3E", - "sKeyName_3F", - "sKeyName_40", - "sKeyName_41", - "sKeyName_42", - "sKeyName_43", - "sKeyName_44", - "sKeyName_45", - "sKeyName_46", - "sKeyName_47", - "sKeyName_48", - "sKeyName_49", - "sKeyName_4A", - "sKeyName_4B", - "sKeyName_4C", - "sKeyName_4D", - "sKeyName_4E", - "sKeyName_4F", - "sKeyName_50", - "sKeyName_51", - "sKeyName_52", - "sKeyName_53", - "sKeyName_54", - "sKeyName_55", - "sKeyName_56", - "sKeyName_57", - "sKeyName_58", - "sKeyName_59", - "sKeyName_5A", - "sKeyName_5B", - "sKeyName_5C", - "sKeyName_5D", - "sKeyName_5E", - "sKeyName_5F", - "sKeyName_60", - "sKeyName_61", - "sKeyName_62", - "sKeyName_63", - "sKeyName_64", - "sKeyName_65", - "sKeyName_66", - "sKeyName_67", - "sKeyName_68", - "sKeyName_69", - "sKeyName_6A", - "sKeyName_6B", - "sKeyName_6C", - "sKeyName_6D", - "sKeyName_6E", - "sKeyName_6F", - "sKeyName_70", - "sKeyName_71", - "sKeyName_72", - "sKeyName_73", - "sKeyName_74", - "sKeyName_75", - "sKeyName_76", - "sKeyName_77", - "sKeyName_78", - "sKeyName_79", - "sKeyName_7A", - "sKeyName_7B", - "sKeyName_7C", - "sKeyName_7D", - "sKeyName_7E", - "sKeyName_7F", - "sKeyName_80", - "sKeyName_81", - "sKeyName_82", - "sKeyName_83", - "sKeyName_84", - "sKeyName_85", - "sKeyName_86", - "sKeyName_87", - "sKeyName_88", - "sKeyName_89", - "sKeyName_8A", - "sKeyName_8B", - "sKeyName_8C", - "sKeyName_8D", - "sKeyName_8E", - "sKeyName_8F", - "sKeyName_90", - "sKeyName_91", - "sKeyName_92", - "sKeyName_93", - "sKeyName_94", - "sKeyName_95", - "sKeyName_96", - "sKeyName_97", - "sKeyName_98", - "sKeyName_99", - "sKeyName_9A", - "sKeyName_9B", - "sKeyName_9C", - "sKeyName_9D", - "sKeyName_9E", - "sKeyName_9F", - "sKeyName_A0", - "sKeyName_A1", - "sKeyName_A2", - "sKeyName_A3", - "sKeyName_A4", - "sKeyName_A5", - "sKeyName_A6", - "sKeyName_A7", - "sKeyName_A8", - "sKeyName_A9", - "sKeyName_AA", - "sKeyName_AB", - "sKeyName_AC", - "sKeyName_AD", - "sKeyName_AE", - "sKeyName_AF", - "sKeyName_B0", - "sKeyName_B1", - "sKeyName_B2", - "sKeyName_B3", - "sKeyName_B4", - "sKeyName_B5", - "sKeyName_B6", - "sKeyName_B7", - "sKeyName_B8", - "sKeyName_B9", - "sKeyName_BA", - "sKeyName_BB", - "sKeyName_BC", - "sKeyName_BD", - "sKeyName_BE", - "sKeyName_BF", - "sKeyName_C0", - "sKeyName_C1", - "sKeyName_C2", - "sKeyName_C3", - "sKeyName_C4", - "sKeyName_C5", - "sKeyName_C6", - "sKeyName_C7", - "sKeyName_C8", - "sKeyName_C9", - "sKeyName_CA", - "sKeyName_CB", - "sKeyName_CC", - "sKeyName_CD", - "sKeyName_CE", - "sKeyName_CF", - "sKeyName_D0", - "sKeyName_D1", - "sKeyName_D2", - "sKeyName_D3", - "sKeyName_D4", - "sKeyName_D5", - "sKeyName_D6", - "sKeyName_D7", - "sKeyName_D8", - "sKeyName_D9", - "sKeyName_DA", - "sKeyName_DB", - "sKeyName_DC", - "sKeyName_DD", - "sKeyName_DE", - "sKeyName_DF", - "sKeyName_E0", - "sKeyName_E1", - "sKeyName_E2", - "sKeyName_E3", - "sKeyName_E4", - "sKeyName_E5", - "sKeyName_E6", - "sKeyName_E7", - "sKeyName_E8", - "sKeyName_E9", - "sKeyName_EA", - "sKeyName_EB", - "sKeyName_EC", - "sKeyName_ED", - "sKeyName_EE", - "sKeyName_EF", - "sKeyName_F0", - "sKeyName_F1", - "sKeyName_F2", - "sKeyName_F3", - "sKeyName_F4", - "sKeyName_F5", - "sKeyName_F6", - "sKeyName_F7", - "sKeyName_F8", - "sKeyName_F9", - "sKeyName_FA", - "sKeyName_FB", - "sKeyName_FC", - "sKeyName_FD", - "sKeyName_FE", - "sKeyName_FF", - "sKeyUsed", - "sKilledEssential", - "sKnight", - "sLeft", - "sLess", - "sLevel", - "sLevelProgress", - "sLevels", - "sLevelUp", - "sLevelUpMenu1", - "sLevelUpMenu2", - "sLevelUpMenu3", - "sLevelUpMenu4", - "sLevelUpMsg", - "sLevitateDisabled", - "sLight", - "sLight_Gamma", - "sLoadFailedMessage", - "sLoadGame", - "sLoadingErrorsMsg", - "sLoadingMessage1", - "sLoadingMessage14", - "sLoadingMessage15", - "sLoadingMessage2", - "sLoadingMessage3", - "sLoadingMessage4", - "sLoadingMessage5", - "sLoadingMessage9", - "sLoadLastSaveMsg", - "sLocal", - "sLockFail", - "sLockImpossible", - "sLockLevel", - "sLockSuccess", - "sLookDownXbox", - "sLookUpXbox", - "sLow", - "sLucDesc", - "sMagDesc", - "sMage", - "sMagic", - "sMagicAncestralGhostID", - "sMagicBonelordID", - "sMagicBoundBattleAxeID", - "sMagicBoundBootsID", - "sMagicBoundCuirassID", - "sMagicBoundDaggerID", - "sMagicBoundHelmID", - "sMagicBoundLeftGauntletID", - "sMagicBoundLongbowID", - "sMagicBoundLongswordID", - "sMagicBoundMaceID", - "sMagicBoundRightGauntletID", - "sMagicBoundShieldID", - "sMagicBoundSpearID", - "sMagicCannotRecast", - "sMagicCenturionSphereID", - "sMagicClannfearID", - "sMagicContractDisease", - "sMagicCorprusWorsens", - "sMagicCreature01ID", - "sMagicCreature02ID", - "sMagicCreature03ID", - "sMagicCreature04ID", - "sMagicCreature05ID", - "sMagicDaedrothID", - "sMagicDremoraID", - "sMagicEffects", - "sMagicFabricantID", - "sMagicFlameAtronachID", - "sMagicFrostAtronachID", - "sMagicGoldenSaintID", - "sMagicGreaterBonewalkerID", - "sMagicHungerID", - "sMagicInsufficientCharge", - "sMagicInsufficientSP", - "sMagicInvalidEffect", - "sMagicInvalidTarget", - "sMagicItem", - "sMagicLeastBonewalkerID", - "sMagicLockSuccess", - "sMagicMenu", - "sMagicOpenSuccess", - "sMagicPCResisted", - "sMagicScampID", - "sMagicSelectTitle", - "sMagicSkeletalMinionID", - "sMagicSkillFail", - "sMagicStormAtronachID", - "sMagicTab", - "sMagicTargetResisted", - "sMagicTargetResistsWeapons", - "sMagicWingedTwilightID", - "sMagnitude", - "sMagnitudeDes", - "sMake Enchantment", - "sMap", - "sMaster", - "sMastPlugMismatchMsg", - "sMaximumSaveGameMessage", - "sMaxSale", - "sMedium", - "sMenu_Help_Delay", - "sMenu_Mode", - "sMenuModeXbox", - "sMenuNextXbox", - "sMenuPrevXbox", - "sMenus", - "sMessage1", - "sMessage2", - "sMessage3", - "sMessage4", - "sMessage5", - "sMessageQuestionAnswer1", - "sMessageQuestionAnswer2", - "sMessageQuestionAnswer3", - "sMiscTab", - "sMissingMastersMsg", - "sMonk", - "sMonthEveningstar", - "sMonthFirstseed", - "sMonthFrostfall", - "sMonthHeartfire", - "sMonthLastseed", - "sMonthMidyear", - "sMonthMorningstar", - "sMonthRainshand", - "sMonthSecondseed", - "sMonthSunsdawn", - "sMonthSunsdusk", - "sMonthSunsheight", - "sMore", - "sMortar", - "sMouse", - "sMouseFlip", - "sMouseWheelDownShort", - "sMouseWheelUpShort", - "sMove", - "sMoveDownXbox", - "sMoveUpXbox", - "sMusic", - "sName", - "sNameTitle", - "sNear", - "sNeedOneSkill", - "sNeedTwoSkills", - "sNewGame", - "sNext", - "sNextRank", - "sNextSpell", - "sNextSpellXbox", - "sNextWeapon", - "sNextWeaponXbox", - "sNightblade", - "sNo", - "sNoName", - "sNone", - "sNotifyMessage1", - "sNotifyMessage10", - "sNotifyMessage11", - "sNotifyMessage12", - "sNotifyMessage13", - "sNotifyMessage14", - "sNotifyMessage15", - "sNotifyMessage16", - "sNotifyMessage16_a", - "sNotifyMessage17", - "sNotifyMessage18", - "sNotifyMessage19", - "sNotifyMessage2", - "sNotifyMessage20", - "sNotifyMessage21", - "sNotifyMessage22", - "sNotifyMessage23", - "sNotifyMessage24", - "sNotifyMessage25", - "sNotifyMessage26", - "sNotifyMessage27", - "sNotifyMessage28", - "sNotifyMessage29", - "sNotifyMessage3", - "sNotifyMessage30", - "sNotifyMessage31", - "sNotifyMessage32", - "sNotifyMessage33", - "sNotifyMessage34", - "sNotifyMessage35", - "sNotifyMessage36", - "sNotifyMessage37", - "sNotifyMessage38", - "sNotifyMessage39", - "sNotifyMessage4", - "sNotifyMessage40", - "sNotifyMessage41", - "sNotifyMessage42", - "sNotifyMessage43", - "sNotifyMessage44", - "sNotifyMessage45", - "sNotifyMessage46", - "sNotifyMessage47", - "sNotifyMessage48", - "sNotifyMessage49", - "sNotifyMessage4XBOX", - "sNotifyMessage5", - "sNotifyMessage50", - "sNotifyMessage51", - "sNotifyMessage52", - "sNotifyMessage53", - "sNotifyMessage54", - "sNotifyMessage55", - "sNotifyMessage56", - "sNotifyMessage57", - "sNotifyMessage58", - "sNotifyMessage59", - "sNotifyMessage6", - "sNotifyMessage60", - "sNotifyMessage61", - "sNotifyMessage62", - "sNotifyMessage63", - "sNotifyMessage64", - "sNotifyMessage65", - "sNotifyMessage66", - "sNotifyMessage67", - "sNotifyMessage6a", - "sNotifyMessage7", - "sNotifyMessage8", - "sNotifyMessage9", - "sOff", - "sOffer", - "sOfferMenuTitle", - "sOK", - "sOn", - "sOnce", - "sOneHanded", - "sOnetypeEffectMessage", - "sonword", - "sOptions", - "sOptionsMenuXbox", - "spercent", - "sPerDesc", - "sPersuasion", - "sPersuasionMenuTitle", - "sPickUp", - "sPilgrim", - "spoint", - "spoints", - "sPotionSuccess", - "sPowerAlreadyUsed", - "sPowers", - "sPreferences", - "sPrefs", - "sPrev", - "sPrevSpell", - "sPrevSpellXbox", - "sPrevWeapon", - "sPrevWeaponXbox", - "sProfitValue", - "sQuality", - "sQuanityMenuMessage01", - "sQuanityMenuMessage02", - "sQuestionDeleteSpell", - "sQuestionMark", - "sQuick0Xbox", - "sQuick10Cmd", - "sQuick1Cmd", - "sQuick2Cmd", - "sQuick3Cmd", - "sQuick4Cmd", - "sQuick4Xbox", - "sQuick5Cmd", - "sQuick5Xbox", - "sQuick6Cmd", - "sQuick6Xbox", - "sQuick7Cmd", - "sQuick7Xbox", - "sQuick8Cmd", - "sQuick8Xbox", - "sQuick9Cmd", - "sQuick9Xbox", - "sQuick_Save", - "sQuickLoadCmd", - "sQuickLoadXbox", - "sQuickMenu", - "sQuickMenu1", - "sQuickMenu2", - "sQuickMenu3", - "sQuickMenu4", - "sQuickMenu5", - "sQuickMenu6", - "sQuickMenuInstruc", - "sQuickMenuTitle", - "sQuickSaveCmd", - "sQuickSaveXbox", - "sRace", - "sRaceMenu1", - "sRaceMenu2", - "sRaceMenu3", - "sRaceMenu4", - "sRaceMenu5", - "sRaceMenu6", - "sRaceMenu7", - "sRacialTraits", - "sRange", - "sRangeDes", - "sRangeSelf", - "sRangeTarget", - "sRangeTouch", - "sReady_Magic", - "sReady_Weapon", - "sReadyItemXbox", - "sReadyMagicXbox", - "sRechargeEnchantment", - "sRender_Distance", - "sRepair", - "sRepairFailed", - "sRepairServiceTitle", - "sRepairSuccess", - "sReputation", - "sResChangeWarning", - "sRest", - "sRestIllegal", - "sRestKey", - "sRestMenu1", - "sRestMenu2", - "sRestMenu3", - "sRestMenu4", - "sRestMenuXbox", - "sRestore", - "sRetort", - "sReturnToGame", - "sRight", - "sRogue", - "sRun", - "sRunXbox", - "sSave", - "sSaveGame", - "sSaveGameDenied", - "sSaveGameFailed", - "sSaveGameNoMemory", - "sSaveGameTooBig", - "sSaveMenu1", - "sSaveMenuHelp01", - "sSaveMenuHelp02", - "sSaveMenuHelp03", - "sSaveMenuHelp04", - "sSaveMenuHelp05", - "sSaveMenuHelp06", - "sSchool", - "sSchoolAlteration", - "sSchoolConjuration", - "sSchoolDestruction", - "sSchoolIllusion", - "sSchoolMysticism", - "sSchoolRestoration", - "sScout", - "sScrolldown", - "sScrollup", - "ssecond", - "sseconds", - "sSeldom", - "sSelect", - "sSell", - "sSellerGold", - "sService", - "sServiceRefusal", - "sServiceRepairTitle", - "sServiceSpellsTitle", - "sServiceTrainingTitle", - "sServiceTrainingWords", - "sServiceTravelTitle", - "sSetValueMessage01", - "sSex", - "sShadows", - "sShadowText", - "sShift", - "sSkill", - "sSkillAcrobatics", - "sSkillAlchemy", - "sSkillAlteration", - "sSkillArmorer", - "sSkillAthletics", - "sSkillAxe", - "sSkillBlock", - "sSkillBluntweapon", - "sSkillClassMajor", - "sSkillClassMinor", - "sSkillClassMisc", - "sSkillConjuration", - "sSkillDestruction", - "sSkillEnchant", - "sSkillHandtohand", - "sSkillHeavyarmor", - "sSkillIllusion", - "sSkillLightarmor", - "sSkillLongblade", - "sSkillMarksman", - "sSkillMaxReached", - "sSkillMediumarmor", - "sSkillMercantile", - "sSkillMysticism", - "sSkillProgress", - "sSkillRestoration", - "sSkillSecurity", - "sSkillShortblade", - "sSkillsMenu1", - "sSkillsMenuReputationHelp", - "sSkillSneak", - "sSkillSpear", - "sSkillSpeechcraft", - "sSkillUnarmored", - "sSlash", - "sSleepInterrupt", - "sSlideLeftXbox", - "sSlideRightXbox", - "sSlow", - "sSorceror", - "sSoulGem", - "sSoulGemsWithSouls", - "sSoultrapSuccess", - "sSpace", - "sSpdDesc", - "sSpecialization", - "sSpecializationCombat", - "sSpecializationMagic", - "sSpecializationMenu1", - "sSpecializationStealth", - "sSpellmaking", - "sSpellmakingHelp1", - "sSpellmakingHelp2", - "sSpellmakingHelp3", - "sSpellmakingHelp4", - "sSpellmakingHelp5", - "sSpellmakingHelp6", - "sSpellmakingMenu1", - "sSpellmakingMenuTitle", - "sSpells", - "sSpellServiceTitle", - "sSpellsword", - "sStartCell", - "sStartCellError", - "sStartError", - "sStats", - "sStrafe", - "sStrDesc", - "sStrip", - "sSubtitles", - "sSystemMenuXbox", - "sTake", - "sTakeAll", - "sTargetCriticalStrike", - "sTaunt", - "sTauntFail", - "sTauntSuccess", - "sTeleportDisabled", - "sThief", - "sThrust", - "sTo", - "sTogglePOVCmd", - "sTogglePOVXbox", - "sToggleRunXbox", - "sTopics", - "sTotalCost", - "sTotalSold", - "sTraining", - "sTrainingServiceTitle", - "sTraits", - "sTransparency_Menu", - "sTrapFail", - "sTrapImpossible", - "sTrapped", - "sTrapSuccess", - "sTravel", - "sTravelServiceTitle", - "sTurn", - "sTurnLeftXbox", - "sTurnRightXbox", - "sTwoHanded", - "sType", - "sTypeAbility", - "sTypeBlightDisease", - "sTypeCurse", - "sTypeDisease", - "sTypePower", - "sTypeSpell", - "sUnequip", - "sUnlocked", - "sUntilHealed", - "sUse", - "sUserDefinedClass", - "sUses", - "sUseXbox", - "sValue", - "sVideo", - "sVideoWarning", - "sVoice", - "sWait", - "sWarrior", - "sWaterReflectUpdate", - "sWaterTerrainReflect", - "sWeaponTab", - "sWeight", - "sWerewolfAlarmMessage", - "sWerewolfPopup", - "sWerewolfRefusal", - "sWerewolfRestMessage", - "sWilDesc", - "sWitchhunter", - "sWorld", - "sWornTab", - "sXStrafe", - "sXTimes", - "sXTimesINT", - "sYes", - "sYourGold", - 0 - }; - - for (int i=0; gmstFloats[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::FloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstFloats[i]; + gmst.mId = CSMWorld::DefaultGMSTs::Floats[i]; gmst.mValue.setType (ESM::VT_Float); - gmst.mValue.setFloat (gmstFloatsValues[i]); + gmst.mValue.setFloat (CSMWorld::DefaultGMSTs::FloatsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (int i=0; gmstIntegers[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::IntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstIntegers[i]; + gmst.mId = CSMWorld::DefaultGMSTs::Ints[i]; gmst.mValue.setType (ESM::VT_Int); - gmst.mValue.setInteger (gmstIntegersValues[i]); + gmst.mValue.setInteger (CSMWorld::DefaultGMSTs::IntsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (int i=0; gmstStrings[i]; i++) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::StringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = gmstStrings[i]; + gmst.mId = CSMWorld::DefaultGMSTs::Strings[i]; gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); getData().getGmsts().add (gmst); @@ -1933,115 +44,28 @@ void CSMDoc::Document::addGmsts() void CSMDoc::Document::addOptionalGmsts() { - static const char *sFloats[] = - { - "fCombatDistanceWerewolfMod", - "fFleeDistance", - "fWereWolfAcrobatics", - "fWereWolfAgility", - "fWereWolfAlchemy", - "fWereWolfAlteration", - "fWereWolfArmorer", - "fWereWolfAthletics", - "fWereWolfAxe", - "fWereWolfBlock", - "fWereWolfBluntWeapon", - "fWereWolfConjuration", - "fWereWolfDestruction", - "fWereWolfEnchant", - "fWereWolfEndurance", - "fWereWolfFatigue", - "fWereWolfHandtoHand", - "fWereWolfHealth", - "fWereWolfHeavyArmor", - "fWereWolfIllusion", - "fWereWolfIntellegence", - "fWereWolfLightArmor", - "fWereWolfLongBlade", - "fWereWolfLuck", - "fWereWolfMagicka", - "fWereWolfMarksman", - "fWereWolfMediumArmor", - "fWereWolfMerchantile", - "fWereWolfMysticism", - "fWereWolfPersonality", - "fWereWolfRestoration", - "fWereWolfRunMult", - "fWereWolfSecurity", - "fWereWolfShortBlade", - "fWereWolfSilverWeaponDamageMult", - "fWereWolfSneak", - "fWereWolfSpear", - "fWereWolfSpeechcraft", - "fWereWolfSpeed", - "fWereWolfStrength", - "fWereWolfUnarmored", - "fWereWolfWillPower", - 0 - }; - - static const char *sIntegers[] = - { - "iWereWolfBounty", - "iWereWolfFightMod", - "iWereWolfFleeMod", - "iWereWolfLevelToAttack", - 0 - }; - - static const char *sStrings[] = - { - "sCompanionShare", - "sCompanionWarningButtonOne", - "sCompanionWarningButtonTwo", - "sCompanionWarningMessage", - "sDeleteNote", - "sEditNote", - "sEffectSummonCreature01", - "sEffectSummonCreature02", - "sEffectSummonCreature03", - "sEffectSummonCreature04", - "sEffectSummonCreature05", - "sEffectSummonFabricant", - "sLevitateDisabled", - "sMagicCreature01ID", - "sMagicCreature02ID", - "sMagicCreature03ID", - "sMagicCreature04ID", - "sMagicCreature05ID", - "sMagicFabricantID", - "sMaxSale", - "sProfitValue", - "sTeleportDisabled", - "sWerewolfAlarmMessage", - "sWerewolfPopup", - "sWerewolfRefusal", - "sWerewolfRestMessage", - 0 - }; - - for (int i=0; sFloats[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalFloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sFloats[i]; + gmst.mId = CSMWorld::DefaultGMSTs::OptionalFloats[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Float); addOptionalGmst (gmst); } - for (int i=0; sIntegers[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalIntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sIntegers[i]; + gmst.mId = CSMWorld::DefaultGMSTs::OptionalInts[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Int); addOptionalGmst (gmst); } - for (int i=0; sStrings[i]; ++i) + for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalStringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = sStrings[i]; + gmst.mId = CSMWorld::DefaultGMSTs::OptionalStrings[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); diff --git a/apps/opencs/model/tools/gmstcheck.cpp b/apps/opencs/model/tools/gmstcheck.cpp new file mode 100644 index 0000000000..6c17ff3a5a --- /dev/null +++ b/apps/opencs/model/tools/gmstcheck.cpp @@ -0,0 +1,91 @@ +#include "gmstcheck.hpp" + +#include "../world/defaultgmsts.hpp" + +CSMTools::GMSTCheckStage::GMSTCheckStage(const CSMWorld::IdCollection& gameSettings) + : mGameSettings(gameSettings) +{} + +int CSMTools::GMSTCheckStage::setup() +{ + return mGameSettings.getSize(); +} + +void CSMTools::GMSTCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = mGameSettings.getRecord (stage); + + if (record.isDeleted()) + return; + + const ESM::GameSetting& gmst = record.get(); + + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Gmst, gmst.mId); + + // Test for empty string + if (gmst.mValue.getType() == ESM::VT_String && gmst.mValue.getString().empty()) + messages.add(id, gmst.mId + " is an empty string", "", CSMDoc::Message::Severity_Warning); + + // Checking type and limits + // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) + if (gmst.mId[0] == 'f') + { + for (size_t i = 0; i < CSMWorld::DefaultGMSTs::FloatCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGMSTs::Floats[i]) + { + if (gmst.mValue.getType() != ESM::VT_Float) + messages.add(id, "The type of " + gmst.mId + " is incorrect", + "Change the GMST type to a float", CSMDoc::Message::Severity_Error); + + if (gmst.mValue.getFloat() < CSMWorld::DefaultGMSTs::FloatLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "Change the value", + CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getFloat() > CSMWorld::DefaultGMSTs::FloatLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "Change the value", + CSMDoc::Message::Severity_Warning); + + break; // for loop + } + } + } + else if (gmst.mId[0] == 'i') + { + for (size_t i = 0; i < CSMWorld::DefaultGMSTs::IntCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGMSTs::Ints[i]) + { + if (gmst.mValue.getType() != ESM::VT_Int) + messages.add(id, "The type of " + gmst.mId + " is incorrect", + "Change the GMST type to an int", CSMDoc::Message::Severity_Error); + + if (gmst.mValue.getInteger() < CSMWorld::DefaultGMSTs::IntLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "Change the value", + CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getInteger() > CSMWorld::DefaultGMSTs::IntLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "Change the value", + CSMDoc::Message::Severity_Warning); + + break; // for loop + } + } + } + else if (gmst.mId[0] == 's') + { + for (size_t i = 0; i < CSMWorld::DefaultGMSTs::StringCount; ++i) + { + if (gmst.mId == CSMWorld::DefaultGMSTs::Strings[i]) + { + ESM::VarType type = gmst.mValue.getType(); + + if (type != ESM::VT_String && type != ESM::VT_None) + messages.add(id, "The type of " + gmst.mId + " is incorrect", + "Change the GMST type to a string", CSMDoc::Message::Severity_Error); + + break; // for loop + } + } + } +} diff --git a/apps/opencs/model/tools/gmstcheck.hpp b/apps/opencs/model/tools/gmstcheck.hpp new file mode 100644 index 0000000000..ca1564d9e8 --- /dev/null +++ b/apps/opencs/model/tools/gmstcheck.hpp @@ -0,0 +1,32 @@ +#ifndef CSM_TOOLS_GMSTCHECK_H +#define CSM_TOOLS_GMSTCHECK_H + +#include + +#include "../world/idcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that GMSTs are alright + class GMSTCheckStage : public CSMDoc::Stage + { + public: + + GMSTCheckStage(const CSMWorld::IdCollection& gameSettings); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mGameSettings; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 608ed9922f..9f36193ef1 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -29,6 +29,7 @@ #include "soundgencheck.hpp" #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" +#include "gmstcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -110,6 +111,8 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); + + mVerifierOperation->appendStage (new GMSTCheckStage (mData.getGmsts())); mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/world/defaultgmsts.cpp b/apps/opencs/model/world/defaultgmsts.cpp new file mode 100644 index 0000000000..ef3d536b1e --- /dev/null +++ b/apps/opencs/model/world/defaultgmsts.cpp @@ -0,0 +1,2336 @@ +#include "defaultgmsts.hpp" + +#include + +const float FInf = std::numeric_limits::infinity(); +const float FEps = std::numeric_limits::epsilon(); + +const int IMax = std::numeric_limits::max(); +const int IMin = std::numeric_limits::min(); + +const char* CSMWorld::DefaultGMSTs::Floats[CSMWorld::DefaultGMSTs::FloatCount] = +{ + "fAIFleeFleeMult", + "fAIFleeHealthMult", + "fAIMagicSpellMult", + "fAIMeleeArmorMult", + "fAIMeleeSummWeaponMult", + "fAIMeleeWeaponMult", + "fAIRangeMagicSpellMult", + "fAIRangeMeleeWeaponMult", + "fAlarmRadius", + "fAthleticsRunBonus", + "fAudioDefaultMaxDistance", + "fAudioDefaultMinDistance", + "fAudioMaxDistanceMult", + "fAudioMinDistanceMult", + "fAudioVoiceDefaultMaxDistance", + "fAudioVoiceDefaultMinDistance", + "fAutoPCSpellChance", + "fAutoSpellChance", + "fBargainOfferBase", + "fBargainOfferMulti", + "fBarterGoldResetDelay", + "fBaseRunMultiplier", + "fBlockStillBonus", + "fBribe1000Mod", + "fBribe100Mod", + "fBribe10Mod", + "fCombatAngleXY", + "fCombatAngleZ", + "fCombatArmorMinMult", + "fCombatBlockLeftAngle", + "fCombatBlockRightAngle", + "fCombatCriticalStrikeMult", + "fCombatDelayCreature", + "fCombatDelayNPC", + "fCombatDistance", + "fCombatDistanceWerewolfMod", + "fCombatForceSideAngle", + "fCombatInvisoMult", + "fCombatKODamageMult", + "fCombatTorsoSideAngle", + "fCombatTorsoStartPercent", + "fCombatTorsoStopPercent", + "fConstantEffectMult", + "fCorpseClearDelay", + "fCorpseRespawnDelay", + "fCrimeGoldDiscountMult", + "fCrimeGoldTurnInMult", + "fCrimeStealing", + "fDamageStrengthBase", + "fDamageStrengthMult", + "fDifficultyMult", + "fDiseaseXferChance", + "fDispAttacking", + "fDispBargainFailMod", + "fDispBargainSuccessMod", + "fDispCrimeMod", + "fDispDiseaseMod", + "fDispFactionMod", + "fDispFactionRankBase", + "fDispFactionRankMult", + "fDispositionMod", + "fDispPersonalityBase", + "fDispPersonalityMult", + "fDispPickPocketMod", + "fDispRaceMod", + "fDispStealing", + "fDispWeaponDrawn", + "fEffectCostMult", + "fElementalShieldMult", + "fEnchantmentChanceMult", + "fEnchantmentConstantChanceMult", + "fEnchantmentConstantDurationMult", + "fEnchantmentMult", + "fEnchantmentValueMult", + "fEncumberedMoveEffect", + "fEncumbranceStrMult", + "fEndFatigueMult", + "fFallAcroBase", + "fFallAcroMult", + "fFallDamageDistanceMin", + "fFallDistanceBase", + "fFallDistanceMult", + "fFatigueAttackBase", + "fFatigueAttackMult", + "fFatigueBase", + "fFatigueBlockBase", + "fFatigueBlockMult", + "fFatigueJumpBase", + "fFatigueJumpMult", + "fFatigueMult", + "fFatigueReturnBase", + "fFatigueReturnMult", + "fFatigueRunBase", + "fFatigueRunMult", + "fFatigueSneakBase", + "fFatigueSneakMult", + "fFatigueSpellBase", + "fFatigueSpellCostMult", + "fFatigueSpellMult", + "fFatigueSwimRunBase", + "fFatigueSwimRunMult", + "fFatigueSwimWalkBase", + "fFatigueSwimWalkMult", + "fFightDispMult", + "fFightDistanceMultiplier", + "fFightStealing", + "fFleeDistance", + "fGreetDistanceReset", + "fHandtoHandHealthPer", + "fHandToHandReach", + "fHoldBreathEndMult", + "fHoldBreathTime", + "fIdleChanceMultiplier", + "fIngredientMult", + "fInteriorHeadTrackMult", + "fJumpAcrobaticsBase", + "fJumpAcroMultiplier", + "fJumpEncumbranceBase", + "fJumpEncumbranceMultiplier", + "fJumpMoveBase", + "fJumpMoveMult", + "fJumpRunMultiplier", + "fKnockDownMult", + "fLevelMod", + "fLevelUpHealthEndMult", + "fLightMaxMod", + "fLuckMod", + "fMagesGuildTravel", + "fMagicCreatureCastDelay", + "fMagicDetectRefreshRate", + "fMagicItemConstantMult", + "fMagicItemCostMult", + "fMagicItemOnceMult", + "fMagicItemPriceMult", + "fMagicItemRechargePerSecond", + "fMagicItemStrikeMult", + "fMagicItemUsedMult", + "fMagicStartIconBlink", + "fMagicSunBlockedMult", + "fMajorSkillBonus", + "fMaxFlySpeed", + "fMaxHandToHandMult", + "fMaxHeadTrackDistance", + "fMaxWalkSpeed", + "fMaxWalkSpeedCreature", + "fMedMaxMod", + "fMessageTimePerChar", + "fMinFlySpeed", + "fMinHandToHandMult", + "fMinorSkillBonus", + "fMinWalkSpeed", + "fMinWalkSpeedCreature", + "fMiscSkillBonus", + "fNPCbaseMagickaMult", + "fNPCHealthBarFade", + "fNPCHealthBarTime", + "fPCbaseMagickaMult", + "fPerDieRollMult", + "fPersonalityMod", + "fPerTempMult", + "fPickLockMult", + "fPickPocketMod", + "fPotionMinUsefulDuration", + "fPotionStrengthMult", + "fPotionT1DurMult", + "fPotionT1MagMult", + "fPotionT4BaseStrengthMult", + "fPotionT4EquipStrengthMult", + "fProjectileMaxSpeed", + "fProjectileMinSpeed", + "fProjectileThrownStoreChance", + "fRepairAmountMult", + "fRepairMult", + "fReputationMod", + "fRestMagicMult", + "fSeriousWoundMult", + "fSleepRandMod", + "fSleepRestMod", + "fSneakBootMult", + "fSneakDistanceBase", + "fSneakDistanceMultiplier", + "fSneakNoViewMult", + "fSneakSkillMult", + "fSneakSpeedMultiplier", + "fSneakUseDelay", + "fSneakUseDist", + "fSneakViewMult", + "fSoulGemMult", + "fSpecialSkillBonus", + "fSpellMakingValueMult", + "fSpellPriceMult", + "fSpellValueMult", + "fStromWalkMult", + "fStromWindSpeed", + "fSuffocationDamage", + "fSwimHeightScale", + "fSwimRunAthleticsMult", + "fSwimRunBase", + "fSwimWalkAthleticsMult", + "fSwimWalkBase", + "fSwingBlockBase", + "fSwingBlockMult", + "fTargetSpellMaxSpeed", + "fThrownWeaponMaxSpeed", + "fThrownWeaponMinSpeed", + "fTrapCostMult", + "fTravelMult", + "fTravelTimeMult", + "fUnarmoredBase1", + "fUnarmoredBase2", + "fVanityDelay", + "fVoiceIdleOdds", + "fWaterReflectUpdateAlways", + "fWaterReflectUpdateSeldom", + "fWeaponDamageMult", + "fWeaponFatigueBlockMult", + "fWeaponFatigueMult", + "fWereWolfAcrobatics", + "fWereWolfAgility", + "fWereWolfAlchemy", + "fWereWolfAlteration", + "fWereWolfArmorer", + "fWereWolfAthletics", + "fWereWolfAxe", + "fWereWolfBlock", + "fWereWolfBluntWeapon", + "fWereWolfConjuration", + "fWereWolfDestruction", + "fWereWolfEnchant", + "fWereWolfEndurance", + "fWereWolfFatigue", + "fWereWolfHandtoHand", + "fWereWolfHealth", + "fWereWolfHeavyArmor", + "fWereWolfIllusion", + "fWereWolfIntellegence", + "fWereWolfLightArmor", + "fWereWolfLongBlade", + "fWereWolfLuck", + "fWereWolfMagicka", + "fWereWolfMarksman", + "fWereWolfMediumArmor", + "fWereWolfMerchantile", + "fWereWolfMysticism", + "fWereWolfPersonality", + "fWereWolfRestoration", + "fWereWolfRunMult", + "fWereWolfSecurity", + "fWereWolfShortBlade", + "fWereWolfSilverWeaponDamageMult", + "fWereWolfSneak", + "fWereWolfSpear", + "fWereWolfSpeechcraft", + "fWereWolfSpeed", + "fWereWolfStrength", + "fWereWolfUnarmored", + "fWereWolfWillPower", + "fWortChanceValue" +}; + +const char * CSMWorld::DefaultGMSTs::Ints[CSMWorld::DefaultGMSTs::IntCount] = +{ + "i1stPersonSneakDelta", + "iAlarmAttack", + "iAlarmKilling", + "iAlarmPickPocket", + "iAlarmStealing", + "iAlarmTresspass", + "iAlchemyMod", + "iAutoPCSpellMax", + "iAutoRepFacMod", + "iAutoRepLevMod", + "iAutoSpellAlterationMax", + "iAutoSpellAttSkillMin", + "iAutoSpellConjurationMax", + "iAutoSpellDestructionMax", + "iAutoSpellIllusionMax", + "iAutoSpellMysticismMax", + "iAutoSpellRestorationMax", + "iAutoSpellTimesCanCast", + "iBarterFailDisposition", + "iBarterSuccessDisposition", + "iBaseArmorSkill", + "iBlockMaxChance", + "iBlockMinChance", + "iBootsWeight", + "iCrimeAttack", + "iCrimeKilling", + "iCrimePickPocket", + "iCrimeThreshold", + "iCrimeThresholdMultiplier", + "iCrimeTresspass", + "iCuirassWeight", + "iDaysinPrisonMod", + "iDispAttackMod", + "iDispKilling", + "iDispTresspass", + "iFightAlarmMult", + "iFightAttack", + "iFightAttacking", + "iFightDistanceBase", + "iFightKilling", + "iFightPickpocket", + "iFightTrespass", + "iFlee", + "iGauntletWeight", + "iGreavesWeight", + "iGreetDistanceMultiplier", + "iGreetDuration", + "iHelmWeight", + "iKnockDownOddsBase", + "iKnockDownOddsMult", + "iLevelUp01Mult", + "iLevelUp02Mult", + "iLevelUp03Mult", + "iLevelUp04Mult", + "iLevelUp05Mult", + "iLevelUp06Mult", + "iLevelUp07Mult", + "iLevelUp08Mult", + "iLevelUp09Mult", + "iLevelUp10Mult", + "iLevelupMajorMult", + "iLevelupMajorMultAttribute", + "iLevelupMinorMult", + "iLevelupMinorMultAttribute", + "iLevelupMiscMultAttriubte", + "iLevelupSpecialization", + "iLevelupTotal", + "iMagicItemChargeConst", + "iMagicItemChargeOnce", + "iMagicItemChargeStrike", + "iMagicItemChargeUse", + "iMaxActivateDist", + "iMaxInfoDist", + "iMonthsToRespawn", + "iNumberCreatures", + "iPauldronWeight", + "iPerMinChance", + "iPerMinChange", + "iPickMaxChance", + "iPickMinChance", + "iShieldWeight", + "iSoulAmountForConstantEffect", + "iTrainingMod", + "iVoiceAttackOdds", + "iVoiceHitOdds", + "iWereWolfBounty", + "iWereWolfFightMod", + "iWereWolfFleeMod", + "iWereWolfLevelToAttack" +}; + +const char * CSMWorld::DefaultGMSTs::Strings[CSMWorld::DefaultGMSTs::StringCount] = +{ + "s3dAudio", + "s3dHardware", + "s3dSoftware", + "sAbsorb", + "sAcrobat", + "sActivate", + "sActivateXbox", + "sActorInCombat", + "sAdmire", + "sAdmireFail", + "sAdmireSuccess", + "sAgent", + "sAgiDesc", + "sAIDistance", + "sAlembic", + "sAllTab", + "sAlways", + "sAlways_Run", + "sand", + "sApparatus", + "sApparelTab", + "sArcher", + "sArea", + "sAreaDes", + "sArmor", + "sArmorRating", + "sAsk", + "sAssassin", + "sAt", + "sAttack", + "sAttributeAgility", + "sAttributeEndurance", + "sAttributeIntelligence", + "sAttributeListTitle", + "sAttributeLuck", + "sAttributePersonality", + "sAttributesMenu1", + "sAttributeSpeed", + "sAttributeStrength", + "sAttributeWillpower", + "sAudio", + "sAuto_Run", + "sBack", + "sBackspace", + "sBackXbox", + "sBarbarian", + "sBard", + "sBarter", + "sBarterDialog1", + "sBarterDialog10", + "sBarterDialog11", + "sBarterDialog12", + "sBarterDialog2", + "sBarterDialog3", + "sBarterDialog4", + "sBarterDialog5", + "sBarterDialog6", + "sBarterDialog7", + "sBarterDialog8", + "sBarterDialog9", + "sBattlemage", + "sBestAttack", + "sBirthSign", + "sBirthsignmenu1", + "sBirthsignmenu2", + "sBlocks", + "sBonusSkillTitle", + "sBookPageOne", + "sBookPageTwo", + "sBookSkillMessage", + "sBounty", + "sBreath", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", + "sBribeFail", + "sBribeSuccess", + "sBuy", + "sBye", + "sCalcinator", + "sCancel", + "sCantEquipWeapWarning", + "sCastCost", + "sCaughtStealingMessage", + "sCenter", + "sChangedMastersMsg", + "sCharges", + "sChooseClassMenu1", + "sChooseClassMenu2", + "sChooseClassMenu3", + "sChooseClassMenu4", + "sChop", + "sClass", + "sClassChoiceMenu1", + "sClassChoiceMenu2", + "sClassChoiceMenu3", + "sClose", + "sCompanionShare", + "sCompanionWarningButtonOne", + "sCompanionWarningButtonTwo", + "sCompanionWarningMessage", + "sCondition", + "sConsoleTitle", + "sContainer", + "sContentsMessage1", + "sContentsMessage2", + "sContentsMessage3", + "sControlerVibration", + "sControls", + "sControlsMenu1", + "sControlsMenu2", + "sControlsMenu3", + "sControlsMenu4", + "sControlsMenu5", + "sControlsMenu6", + "sCostChance", + "sCostCharge", + "sCreate", + "sCreateClassMenu1", + "sCreateClassMenu2", + "sCreateClassMenu3", + "sCreateClassMenuHelp1", + "sCreateClassMenuHelp2", + "sCreateClassMenuWarning", + "sCreatedEffects", + "sCrimeHelp", + "sCrimeMessage", + "sCrouch_Sneak", + "sCrouchXbox", + "sCrusader", + "sCursorOff", + "sCustom", + "sCustomClassName", + "sDamage", + "sDark_Gamma", + "sDay", + "sDefaultCellname", + "sDelete", + "sDeleteGame", + "sDeleteNote", + "sDeleteSpell", + "sDeleteSpellError", + "sDetail_Level", + "sDialogMenu1", + "sDialogText1Xbox", + "sDialogText2Xbox", + "sDialogText3Xbox", + "sDifficulty", + "sDisposeCorpseFail", + "sDisposeofCorpse", + "sDone", + "sDoYouWantTo", + "sDrain", + "sDrop", + "sDuration", + "sDurationDes", + "sEasy", + "sEditNote", + "sEffectAbsorbAttribute", + "sEffectAbsorbFatigue", + "sEffectAbsorbHealth", + "sEffectAbsorbSkill", + "sEffectAbsorbSpellPoints", + "sEffectAlmsiviIntervention", + "sEffectBlind", + "sEffectBoundBattleAxe", + "sEffectBoundBoots", + "sEffectBoundCuirass", + "sEffectBoundDagger", + "sEffectBoundGloves", + "sEffectBoundHelm", + "sEffectBoundLongbow", + "sEffectBoundLongsword", + "sEffectBoundMace", + "sEffectBoundShield", + "sEffectBoundSpear", + "sEffectBurden", + "sEffectCalmCreature", + "sEffectCalmHumanoid", + "sEffectChameleon", + "sEffectCharm", + "sEffectCommandCreatures", + "sEffectCommandHumanoids", + "sEffectCorpus", + "sEffectCureBlightDisease", + "sEffectCureCommonDisease", + "sEffectCureCorprusDisease", + "sEffectCureParalyzation", + "sEffectCurePoison", + "sEffectDamageAttribute", + "sEffectDamageFatigue", + "sEffectDamageHealth", + "sEffectDamageMagicka", + "sEffectDamageSkill", + "sEffectDemoralizeCreature", + "sEffectDemoralizeHumanoid", + "sEffectDetectAnimal", + "sEffectDetectEnchantment", + "sEffectDetectKey", + "sEffectDisintegrateArmor", + "sEffectDisintegrateWeapon", + "sEffectDispel", + "sEffectDivineIntervention", + "sEffectDrainAttribute", + "sEffectDrainFatigue", + "sEffectDrainHealth", + "sEffectDrainSkill", + "sEffectDrainSpellpoints", + "sEffectExtraSpell", + "sEffectFeather", + "sEffectFireDamage", + "sEffectFireShield", + "sEffectFortifyAttackBonus", + "sEffectFortifyAttribute", + "sEffectFortifyFatigue", + "sEffectFortifyHealth", + "sEffectFortifyMagickaMultiplier", + "sEffectFortifySkill", + "sEffectFortifySpellpoints", + "sEffectFrenzyCreature", + "sEffectFrenzyHumanoid", + "sEffectFrostDamage", + "sEffectFrostShield", + "sEffectInvisibility", + "sEffectJump", + "sEffectLevitate", + "sEffectLight", + "sEffectLightningShield", + "sEffectLock", + "sEffectMark", + "sEffectNightEye", + "sEffectOpen", + "sEffectParalyze", + "sEffectPoison", + "sEffectRallyCreature", + "sEffectRallyHumanoid", + "sEffectRecall", + "sEffectReflect", + "sEffectRemoveCurse", + "sEffectResistBlightDisease", + "sEffectResistCommonDisease", + "sEffectResistCorprusDisease", + "sEffectResistFire", + "sEffectResistFrost", + "sEffectResistMagicka", + "sEffectResistNormalWeapons", + "sEffectResistParalysis", + "sEffectResistPoison", + "sEffectResistShock", + "sEffectRestoreAttribute", + "sEffectRestoreFatigue", + "sEffectRestoreHealth", + "sEffectRestoreSkill", + "sEffectRestoreSpellPoints", + "sEffects", + "sEffectSanctuary", + "sEffectShield", + "sEffectShockDamage", + "sEffectSilence", + "sEffectSlowFall", + "sEffectSoultrap", + "sEffectSound", + "sEffectSpellAbsorption", + "sEffectStuntedMagicka", + "sEffectSummonAncestralGhost", + "sEffectSummonBonelord", + "sEffectSummonCenturionSphere", + "sEffectSummonClannfear", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05", + "sEffectSummonDaedroth", + "sEffectSummonDremora", + "sEffectSummonFabricant", + "sEffectSummonFlameAtronach", + "sEffectSummonFrostAtronach", + "sEffectSummonGoldensaint", + "sEffectSummonGreaterBonewalker", + "sEffectSummonHunger", + "sEffectSummonLeastBonewalker", + "sEffectSummonScamp", + "sEffectSummonSkeletalMinion", + "sEffectSummonStormAtronach", + "sEffectSummonWingedTwilight", + "sEffectSunDamage", + "sEffectSwiftSwim", + "sEffectTelekinesis", + "sEffectTurnUndead", + "sEffectVampirism", + "sEffectWaterBreathing", + "sEffectWaterWalking", + "sEffectWeaknessToBlightDisease", + "sEffectWeaknessToCommonDisease", + "sEffectWeaknessToCorprusDisease", + "sEffectWeaknessToFire", + "sEffectWeaknessToFrost", + "sEffectWeaknessToMagicka", + "sEffectWeaknessToNormalWeapons", + "sEffectWeaknessToPoison", + "sEffectWeaknessToShock", + "sEnableJoystick", + "sEnchanting", + "sEnchantItems", + "sEnchantmentHelp1", + "sEnchantmentHelp10", + "sEnchantmentHelp2", + "sEnchantmentHelp3", + "sEnchantmentHelp4", + "sEnchantmentHelp5", + "sEnchantmentHelp6", + "sEnchantmentHelp7", + "sEnchantmentHelp8", + "sEnchantmentHelp9", + "sEnchantmentMenu1", + "sEnchantmentMenu10", + "sEnchantmentMenu11", + "sEnchantmentMenu12", + "sEnchantmentMenu2", + "sEnchantmentMenu3", + "sEnchantmentMenu4", + "sEnchantmentMenu5", + "sEnchantmentMenu6", + "sEnchantmentMenu7", + "sEnchantmentMenu8", + "sEnchantmentMenu9", + "sEncumbrance", + "sEndDesc", + "sEquip", + "sExitGame", + "sExpelled", + "sExpelledMessage", + "sFace", + "sFaction", + "sFar", + "sFast", + "sFatDesc", + "sFatigue", + "sFavoriteSkills", + "sfeet", + "sFileSize", + "sfootarea", + "sFootsteps", + "sfor", + "sFortify", + "sForward", + "sForwardXbox", + "sFull", + "sGame", + "sGameWithoutLauncherXbox", + "sGamma_Correction", + "sGeneralMastPlugMismatchMsg", + "sGold", + "sGoodbye", + "sGoverningAttribute", + "sgp", + "sHair", + "sHard", + "sHeal", + "sHealer", + "sHealth", + "sHealthDesc", + "sHealthPerHourOfRest", + "sHealthPerLevel", + "sHeavy", + "sHigh", + "sin", + "sInfo", + "sInfoRefusal", + "sIngredients", + "sInPrisonTitle", + "sInputMenu1", + "sIntDesc", + "sIntimidate", + "sIntimidateFail", + "sIntimidateSuccess", + "sInvalidSaveGameMsg", + "sInvalidSaveGameMsgXBOX", + "sInventory", + "sInventoryMenu1", + "sInventoryMessage1", + "sInventoryMessage2", + "sInventoryMessage3", + "sInventoryMessage4", + "sInventoryMessage5", + "sInventorySelectNoIngredients", + "sInventorySelectNoItems", + "sInventorySelectNoSoul", + "sItem", + "sItemCastConstant", + "sItemCastOnce", + "sItemCastWhenStrikes", + "sItemCastWhenUsed", + "sItemName", + "sJournal", + "sJournalCmd", + "sJournalEntry", + "sJournalXbox", + "sJoystickHatShort", + "sJoystickNotFound", + "sJoystickShort", + "sJump", + "sJumpXbox", + "sKeyName_00", + "sKeyName_01", + "sKeyName_02", + "sKeyName_03", + "sKeyName_04", + "sKeyName_05", + "sKeyName_06", + "sKeyName_07", + "sKeyName_08", + "sKeyName_09", + "sKeyName_0A", + "sKeyName_0B", + "sKeyName_0C", + "sKeyName_0D", + "sKeyName_0E", + "sKeyName_0F", + "sKeyName_10", + "sKeyName_11", + "sKeyName_12", + "sKeyName_13", + "sKeyName_14", + "sKeyName_15", + "sKeyName_16", + "sKeyName_17", + "sKeyName_18", + "sKeyName_19", + "sKeyName_1A", + "sKeyName_1B", + "sKeyName_1C", + "sKeyName_1D", + "sKeyName_1E", + "sKeyName_1F", + "sKeyName_20", + "sKeyName_21", + "sKeyName_22", + "sKeyName_23", + "sKeyName_24", + "sKeyName_25", + "sKeyName_26", + "sKeyName_27", + "sKeyName_28", + "sKeyName_29", + "sKeyName_2A", + "sKeyName_2B", + "sKeyName_2C", + "sKeyName_2D", + "sKeyName_2E", + "sKeyName_2F", + "sKeyName_30", + "sKeyName_31", + "sKeyName_32", + "sKeyName_33", + "sKeyName_34", + "sKeyName_35", + "sKeyName_36", + "sKeyName_37", + "sKeyName_38", + "sKeyName_39", + "sKeyName_3A", + "sKeyName_3B", + "sKeyName_3C", + "sKeyName_3D", + "sKeyName_3E", + "sKeyName_3F", + "sKeyName_40", + "sKeyName_41", + "sKeyName_42", + "sKeyName_43", + "sKeyName_44", + "sKeyName_45", + "sKeyName_46", + "sKeyName_47", + "sKeyName_48", + "sKeyName_49", + "sKeyName_4A", + "sKeyName_4B", + "sKeyName_4C", + "sKeyName_4D", + "sKeyName_4E", + "sKeyName_4F", + "sKeyName_50", + "sKeyName_51", + "sKeyName_52", + "sKeyName_53", + "sKeyName_54", + "sKeyName_55", + "sKeyName_56", + "sKeyName_57", + "sKeyName_58", + "sKeyName_59", + "sKeyName_5A", + "sKeyName_5B", + "sKeyName_5C", + "sKeyName_5D", + "sKeyName_5E", + "sKeyName_5F", + "sKeyName_60", + "sKeyName_61", + "sKeyName_62", + "sKeyName_63", + "sKeyName_64", + "sKeyName_65", + "sKeyName_66", + "sKeyName_67", + "sKeyName_68", + "sKeyName_69", + "sKeyName_6A", + "sKeyName_6B", + "sKeyName_6C", + "sKeyName_6D", + "sKeyName_6E", + "sKeyName_6F", + "sKeyName_70", + "sKeyName_71", + "sKeyName_72", + "sKeyName_73", + "sKeyName_74", + "sKeyName_75", + "sKeyName_76", + "sKeyName_77", + "sKeyName_78", + "sKeyName_79", + "sKeyName_7A", + "sKeyName_7B", + "sKeyName_7C", + "sKeyName_7D", + "sKeyName_7E", + "sKeyName_7F", + "sKeyName_80", + "sKeyName_81", + "sKeyName_82", + "sKeyName_83", + "sKeyName_84", + "sKeyName_85", + "sKeyName_86", + "sKeyName_87", + "sKeyName_88", + "sKeyName_89", + "sKeyName_8A", + "sKeyName_8B", + "sKeyName_8C", + "sKeyName_8D", + "sKeyName_8E", + "sKeyName_8F", + "sKeyName_90", + "sKeyName_91", + "sKeyName_92", + "sKeyName_93", + "sKeyName_94", + "sKeyName_95", + "sKeyName_96", + "sKeyName_97", + "sKeyName_98", + "sKeyName_99", + "sKeyName_9A", + "sKeyName_9B", + "sKeyName_9C", + "sKeyName_9D", + "sKeyName_9E", + "sKeyName_9F", + "sKeyName_A0", + "sKeyName_A1", + "sKeyName_A2", + "sKeyName_A3", + "sKeyName_A4", + "sKeyName_A5", + "sKeyName_A6", + "sKeyName_A7", + "sKeyName_A8", + "sKeyName_A9", + "sKeyName_AA", + "sKeyName_AB", + "sKeyName_AC", + "sKeyName_AD", + "sKeyName_AE", + "sKeyName_AF", + "sKeyName_B0", + "sKeyName_B1", + "sKeyName_B2", + "sKeyName_B3", + "sKeyName_B4", + "sKeyName_B5", + "sKeyName_B6", + "sKeyName_B7", + "sKeyName_B8", + "sKeyName_B9", + "sKeyName_BA", + "sKeyName_BB", + "sKeyName_BC", + "sKeyName_BD", + "sKeyName_BE", + "sKeyName_BF", + "sKeyName_C0", + "sKeyName_C1", + "sKeyName_C2", + "sKeyName_C3", + "sKeyName_C4", + "sKeyName_C5", + "sKeyName_C6", + "sKeyName_C7", + "sKeyName_C8", + "sKeyName_C9", + "sKeyName_CA", + "sKeyName_CB", + "sKeyName_CC", + "sKeyName_CD", + "sKeyName_CE", + "sKeyName_CF", + "sKeyName_D0", + "sKeyName_D1", + "sKeyName_D2", + "sKeyName_D3", + "sKeyName_D4", + "sKeyName_D5", + "sKeyName_D6", + "sKeyName_D7", + "sKeyName_D8", + "sKeyName_D9", + "sKeyName_DA", + "sKeyName_DB", + "sKeyName_DC", + "sKeyName_DD", + "sKeyName_DE", + "sKeyName_DF", + "sKeyName_E0", + "sKeyName_E1", + "sKeyName_E2", + "sKeyName_E3", + "sKeyName_E4", + "sKeyName_E5", + "sKeyName_E6", + "sKeyName_E7", + "sKeyName_E8", + "sKeyName_E9", + "sKeyName_EA", + "sKeyName_EB", + "sKeyName_EC", + "sKeyName_ED", + "sKeyName_EE", + "sKeyName_EF", + "sKeyName_F0", + "sKeyName_F1", + "sKeyName_F2", + "sKeyName_F3", + "sKeyName_F4", + "sKeyName_F5", + "sKeyName_F6", + "sKeyName_F7", + "sKeyName_F8", + "sKeyName_F9", + "sKeyName_FA", + "sKeyName_FB", + "sKeyName_FC", + "sKeyName_FD", + "sKeyName_FE", + "sKeyName_FF", + "sKeyUsed", + "sKilledEssential", + "sKnight", + "sLeft", + "sLess", + "sLevel", + "sLevelProgress", + "sLevels", + "sLevelUp", + "sLevelUpMenu1", + "sLevelUpMenu2", + "sLevelUpMenu3", + "sLevelUpMenu4", + "sLevelUpMsg", + "sLevitateDisabled", + "sLight", + "sLight_Gamma", + "sLoadFailedMessage", + "sLoadGame", + "sLoadingErrorsMsg", + "sLoadingMessage1", + "sLoadingMessage14", + "sLoadingMessage15", + "sLoadingMessage2", + "sLoadingMessage3", + "sLoadingMessage4", + "sLoadingMessage5", + "sLoadingMessage9", + "sLoadLastSaveMsg", + "sLocal", + "sLockFail", + "sLockImpossible", + "sLockLevel", + "sLockSuccess", + "sLookDownXbox", + "sLookUpXbox", + "sLow", + "sLucDesc", + "sMagDesc", + "sMage", + "sMagic", + "sMagicAncestralGhostID", + "sMagicBonelordID", + "sMagicBoundBattleAxeID", + "sMagicBoundBootsID", + "sMagicBoundCuirassID", + "sMagicBoundDaggerID", + "sMagicBoundHelmID", + "sMagicBoundLeftGauntletID", + "sMagicBoundLongbowID", + "sMagicBoundLongswordID", + "sMagicBoundMaceID", + "sMagicBoundRightGauntletID", + "sMagicBoundShieldID", + "sMagicBoundSpearID", + "sMagicCannotRecast", + "sMagicCenturionSphereID", + "sMagicClannfearID", + "sMagicContractDisease", + "sMagicCorprusWorsens", + "sMagicCreature01ID", + "sMagicCreature02ID", + "sMagicCreature03ID", + "sMagicCreature04ID", + "sMagicCreature05ID", + "sMagicDaedrothID", + "sMagicDremoraID", + "sMagicEffects", + "sMagicFabricantID", + "sMagicFlameAtronachID", + "sMagicFrostAtronachID", + "sMagicGoldenSaintID", + "sMagicGreaterBonewalkerID", + "sMagicHungerID", + "sMagicInsufficientCharge", + "sMagicInsufficientSP", + "sMagicInvalidEffect", + "sMagicInvalidTarget", + "sMagicItem", + "sMagicLeastBonewalkerID", + "sMagicLockSuccess", + "sMagicMenu", + "sMagicOpenSuccess", + "sMagicPCResisted", + "sMagicScampID", + "sMagicSelectTitle", + "sMagicSkeletalMinionID", + "sMagicSkillFail", + "sMagicStormAtronachID", + "sMagicTab", + "sMagicTargetResisted", + "sMagicTargetResistsWeapons", + "sMagicWingedTwilightID", + "sMagnitude", + "sMagnitudeDes", + "sMake Enchantment", + "sMap", + "sMaster", + "sMastPlugMismatchMsg", + "sMaximumSaveGameMessage", + "sMaxSale", + "sMedium", + "sMenu_Help_Delay", + "sMenu_Mode", + "sMenuModeXbox", + "sMenuNextXbox", + "sMenuPrevXbox", + "sMenus", + "sMessage1", + "sMessage2", + "sMessage3", + "sMessage4", + "sMessage5", + "sMessageQuestionAnswer1", + "sMessageQuestionAnswer2", + "sMessageQuestionAnswer3", + "sMiscTab", + "sMissingMastersMsg", + "sMonk", + "sMonthEveningstar", + "sMonthFirstseed", + "sMonthFrostfall", + "sMonthHeartfire", + "sMonthLastseed", + "sMonthMidyear", + "sMonthMorningstar", + "sMonthRainshand", + "sMonthSecondseed", + "sMonthSunsdawn", + "sMonthSunsdusk", + "sMonthSunsheight", + "sMore", + "sMortar", + "sMouse", + "sMouseFlip", + "sMouseWheelDownShort", + "sMouseWheelUpShort", + "sMove", + "sMoveDownXbox", + "sMoveUpXbox", + "sMusic", + "sName", + "sNameTitle", + "sNear", + "sNeedOneSkill", + "sNeedTwoSkills", + "sNewGame", + "sNext", + "sNextRank", + "sNextSpell", + "sNextSpellXbox", + "sNextWeapon", + "sNextWeaponXbox", + "sNightblade", + "sNo", + "sNoName", + "sNone", + "sNotifyMessage1", + "sNotifyMessage10", + "sNotifyMessage11", + "sNotifyMessage12", + "sNotifyMessage13", + "sNotifyMessage14", + "sNotifyMessage15", + "sNotifyMessage16", + "sNotifyMessage16_a", + "sNotifyMessage17", + "sNotifyMessage18", + "sNotifyMessage19", + "sNotifyMessage2", + "sNotifyMessage20", + "sNotifyMessage21", + "sNotifyMessage22", + "sNotifyMessage23", + "sNotifyMessage24", + "sNotifyMessage25", + "sNotifyMessage26", + "sNotifyMessage27", + "sNotifyMessage28", + "sNotifyMessage29", + "sNotifyMessage3", + "sNotifyMessage30", + "sNotifyMessage31", + "sNotifyMessage32", + "sNotifyMessage33", + "sNotifyMessage34", + "sNotifyMessage35", + "sNotifyMessage36", + "sNotifyMessage37", + "sNotifyMessage38", + "sNotifyMessage39", + "sNotifyMessage4", + "sNotifyMessage40", + "sNotifyMessage41", + "sNotifyMessage42", + "sNotifyMessage43", + "sNotifyMessage44", + "sNotifyMessage45", + "sNotifyMessage46", + "sNotifyMessage47", + "sNotifyMessage48", + "sNotifyMessage49", + "sNotifyMessage4XBOX", + "sNotifyMessage5", + "sNotifyMessage50", + "sNotifyMessage51", + "sNotifyMessage52", + "sNotifyMessage53", + "sNotifyMessage54", + "sNotifyMessage55", + "sNotifyMessage56", + "sNotifyMessage57", + "sNotifyMessage58", + "sNotifyMessage59", + "sNotifyMessage6", + "sNotifyMessage60", + "sNotifyMessage61", + "sNotifyMessage62", + "sNotifyMessage63", + "sNotifyMessage64", + "sNotifyMessage65", + "sNotifyMessage66", + "sNotifyMessage67", + "sNotifyMessage6a", + "sNotifyMessage7", + "sNotifyMessage8", + "sNotifyMessage9", + "sOff", + "sOffer", + "sOfferMenuTitle", + "sOK", + "sOn", + "sOnce", + "sOneHanded", + "sOnetypeEffectMessage", + "sonword", + "sOptions", + "sOptionsMenuXbox", + "spercent", + "sPerDesc", + "sPersuasion", + "sPersuasionMenuTitle", + "sPickUp", + "sPilgrim", + "spoint", + "spoints", + "sPotionSuccess", + "sPowerAlreadyUsed", + "sPowers", + "sPreferences", + "sPrefs", + "sPrev", + "sPrevSpell", + "sPrevSpellXbox", + "sPrevWeapon", + "sPrevWeaponXbox", + "sProfitValue", + "sQuality", + "sQuanityMenuMessage01", + "sQuanityMenuMessage02", + "sQuestionDeleteSpell", + "sQuestionMark", + "sQuick0Xbox", + "sQuick10Cmd", + "sQuick1Cmd", + "sQuick2Cmd", + "sQuick3Cmd", + "sQuick4Cmd", + "sQuick4Xbox", + "sQuick5Cmd", + "sQuick5Xbox", + "sQuick6Cmd", + "sQuick6Xbox", + "sQuick7Cmd", + "sQuick7Xbox", + "sQuick8Cmd", + "sQuick8Xbox", + "sQuick9Cmd", + "sQuick9Xbox", + "sQuick_Save", + "sQuickLoadCmd", + "sQuickLoadXbox", + "sQuickMenu", + "sQuickMenu1", + "sQuickMenu2", + "sQuickMenu3", + "sQuickMenu4", + "sQuickMenu5", + "sQuickMenu6", + "sQuickMenuInstruc", + "sQuickMenuTitle", + "sQuickSaveCmd", + "sQuickSaveXbox", + "sRace", + "sRaceMenu1", + "sRaceMenu2", + "sRaceMenu3", + "sRaceMenu4", + "sRaceMenu5", + "sRaceMenu6", + "sRaceMenu7", + "sRacialTraits", + "sRange", + "sRangeDes", + "sRangeSelf", + "sRangeTarget", + "sRangeTouch", + "sReady_Magic", + "sReady_Weapon", + "sReadyItemXbox", + "sReadyMagicXbox", + "sRechargeEnchantment", + "sRender_Distance", + "sRepair", + "sRepairFailed", + "sRepairServiceTitle", + "sRepairSuccess", + "sReputation", + "sResChangeWarning", + "sRest", + "sRestIllegal", + "sRestKey", + "sRestMenu1", + "sRestMenu2", + "sRestMenu3", + "sRestMenu4", + "sRestMenuXbox", + "sRestore", + "sRetort", + "sReturnToGame", + "sRight", + "sRogue", + "sRun", + "sRunXbox", + "sSave", + "sSaveGame", + "sSaveGameDenied", + "sSaveGameFailed", + "sSaveGameNoMemory", + "sSaveGameTooBig", + "sSaveMenu1", + "sSaveMenuHelp01", + "sSaveMenuHelp02", + "sSaveMenuHelp03", + "sSaveMenuHelp04", + "sSaveMenuHelp05", + "sSaveMenuHelp06", + "sSchool", + "sSchoolAlteration", + "sSchoolConjuration", + "sSchoolDestruction", + "sSchoolIllusion", + "sSchoolMysticism", + "sSchoolRestoration", + "sScout", + "sScrolldown", + "sScrollup", + "ssecond", + "sseconds", + "sSeldom", + "sSelect", + "sSell", + "sSellerGold", + "sService", + "sServiceRefusal", + "sServiceRepairTitle", + "sServiceSpellsTitle", + "sServiceTrainingTitle", + "sServiceTrainingWords", + "sServiceTravelTitle", + "sSetValueMessage01", + "sSex", + "sShadows", + "sShadowText", + "sShift", + "sSkill", + "sSkillAcrobatics", + "sSkillAlchemy", + "sSkillAlteration", + "sSkillArmorer", + "sSkillAthletics", + "sSkillAxe", + "sSkillBlock", + "sSkillBluntweapon", + "sSkillClassMajor", + "sSkillClassMinor", + "sSkillClassMisc", + "sSkillConjuration", + "sSkillDestruction", + "sSkillEnchant", + "sSkillHandtohand", + "sSkillHeavyarmor", + "sSkillIllusion", + "sSkillLightarmor", + "sSkillLongblade", + "sSkillMarksman", + "sSkillMaxReached", + "sSkillMediumarmor", + "sSkillMercantile", + "sSkillMysticism", + "sSkillProgress", + "sSkillRestoration", + "sSkillSecurity", + "sSkillShortblade", + "sSkillsMenu1", + "sSkillsMenuReputationHelp", + "sSkillSneak", + "sSkillSpear", + "sSkillSpeechcraft", + "sSkillUnarmored", + "sSlash", + "sSleepInterrupt", + "sSlideLeftXbox", + "sSlideRightXbox", + "sSlow", + "sSorceror", + "sSoulGem", + "sSoulGemsWithSouls", + "sSoultrapSuccess", + "sSpace", + "sSpdDesc", + "sSpecialization", + "sSpecializationCombat", + "sSpecializationMagic", + "sSpecializationMenu1", + "sSpecializationStealth", + "sSpellmaking", + "sSpellmakingHelp1", + "sSpellmakingHelp2", + "sSpellmakingHelp3", + "sSpellmakingHelp4", + "sSpellmakingHelp5", + "sSpellmakingHelp6", + "sSpellmakingMenu1", + "sSpellmakingMenuTitle", + "sSpells", + "sSpellServiceTitle", + "sSpellsword", + "sStartCell", + "sStartCellError", + "sStartError", + "sStats", + "sStrafe", + "sStrDesc", + "sStrip", + "sSubtitles", + "sSystemMenuXbox", + "sTake", + "sTakeAll", + "sTargetCriticalStrike", + "sTaunt", + "sTauntFail", + "sTauntSuccess", + "sTeleportDisabled", + "sThief", + "sThrust", + "sTo", + "sTogglePOVCmd", + "sTogglePOVXbox", + "sToggleRunXbox", + "sTopics", + "sTotalCost", + "sTotalSold", + "sTraining", + "sTrainingServiceTitle", + "sTraits", + "sTransparency_Menu", + "sTrapFail", + "sTrapImpossible", + "sTrapped", + "sTrapSuccess", + "sTravel", + "sTravelServiceTitle", + "sTurn", + "sTurnLeftXbox", + "sTurnRightXbox", + "sTwoHanded", + "sType", + "sTypeAbility", + "sTypeBlightDisease", + "sTypeCurse", + "sTypeDisease", + "sTypePower", + "sTypeSpell", + "sUnequip", + "sUnlocked", + "sUntilHealed", + "sUse", + "sUserDefinedClass", + "sUses", + "sUseXbox", + "sValue", + "sVideo", + "sVideoWarning", + "sVoice", + "sWait", + "sWarrior", + "sWaterReflectUpdate", + "sWaterTerrainReflect", + "sWeaponTab", + "sWeight", + "sWerewolfAlarmMessage", + "sWerewolfPopup", + "sWerewolfRefusal", + "sWerewolfRestMessage", + "sWilDesc", + "sWitchhunter", + "sWorld", + "sWornTab", + "sXStrafe", + "sXTimes", + "sXTimesINT", + "sYes", + "sYourGold" +}; + +const char * CSMWorld::DefaultGMSTs::OptionalFloats[CSMWorld::DefaultGMSTs::OptionalFloatCount] = +{ + "fCombatDistanceWerewolfMod", + "fFleeDistance", + "fWereWolfAcrobatics", + "fWereWolfAgility", + "fWereWolfAlchemy", + "fWereWolfAlteration", + "fWereWolfArmorer", + "fWereWolfAthletics", + "fWereWolfAxe", + "fWereWolfBlock", + "fWereWolfBluntWeapon", + "fWereWolfConjuration", + "fWereWolfDestruction", + "fWereWolfEnchant", + "fWereWolfEndurance", + "fWereWolfFatigue", + "fWereWolfHandtoHand", + "fWereWolfHealth", + "fWereWolfHeavyArmor", + "fWereWolfIllusion", + "fWereWolfIntellegence", + "fWereWolfLightArmor", + "fWereWolfLongBlade", + "fWereWolfLuck", + "fWereWolfMagicka", + "fWereWolfMarksman", + "fWereWolfMediumArmor", + "fWereWolfMerchantile", + "fWereWolfMysticism", + "fWereWolfPersonality", + "fWereWolfRestoration", + "fWereWolfRunMult", + "fWereWolfSecurity", + "fWereWolfShortBlade", + "fWereWolfSilverWeaponDamageMult", + "fWereWolfSneak", + "fWereWolfSpear", + "fWereWolfSpeechcraft", + "fWereWolfSpeed", + "fWereWolfStrength", + "fWereWolfUnarmored", + "fWereWolfWillPower" +}; + +const char * CSMWorld::DefaultGMSTs::OptionalInts[CSMWorld::DefaultGMSTs::OptionalIntCount] = +{ + "iWereWolfBounty", + "iWereWolfFightMod", + "iWereWolfFleeMod", + "iWereWolfLevelToAttack" +}; + +const char * CSMWorld::DefaultGMSTs::OptionalStrings[CSMWorld::DefaultGMSTs::OptionalStringCount] = +{ + "sCompanionShare", + "sCompanionWarningButtonOne", + "sCompanionWarningButtonTwo", + "sCompanionWarningMessage", + "sDeleteNote", + "sEditNote", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05", + "sEffectSummonFabricant", + "sLevitateDisabled", + "sMagicCreature01ID", + "sMagicCreature02ID", + "sMagicCreature03ID", + "sMagicCreature04ID", + "sMagicCreature05ID", + "sMagicFabricantID", + "sMaxSale", + "sProfitValue", + "sTeleportDisabled", + "sWerewolfAlarmMessage", + "sWerewolfPopup", + "sWerewolfRefusal", + "sWerewolfRestMessage" +}; + +const float CSMWorld::DefaultGMSTs::FloatsDefaultValues[CSMWorld::DefaultGMSTs::FloatCount] = +{ + 0.3, // fAIFleeFleeMult + 7.0, // fAIFleeHealthMult + 3.0, // fAIMagicSpellMult + 1.0, // fAIMeleeArmorMult + 1.0, // fAIMeleeSummWeaponMult + 2.0, // fAIMeleeWeaponMult + 5.0, // fAIRangeMagicSpellMult + 5.0, // fAIRangeMeleeWeaponMult + 2000.0, // fAlarmRadius + 1.0, // fAthleticsRunBonus + 40.0, // fAudioDefaultMaxDistance + 5.0, // fAudioDefaultMinDistance + 50.0, // fAudioMaxDistanceMult + 20.0, // fAudioMinDistanceMult + 60.0, // fAudioVoiceDefaultMaxDistance + 10.0, // fAudioVoiceDefaultMinDistance + 50.0, // fAutoPCSpellChance + 80.0, // fAutoSpellChance + 50.0, // fBargainOfferBase + -4.0, // fBargainOfferMulti + 24.0, // fBarterGoldResetDelay + 1.75, // fBaseRunMultiplier + 1.25, // fBlockStillBonus + 150.0, // fBribe1000Mod + 75.0, // fBribe100Mod + 35.0, // fBribe10Mod + 60.0, // fCombatAngleXY + 60.0, // fCombatAngleZ + 0.25, // fCombatArmorMinMult + -90.0, // fCombatBlockLeftAngle + 30.0, // fCombatBlockRightAngle + 4.0, // fCombatCriticalStrikeMult + 0.1, // fCombatDelayCreature + 0.1, // fCombatDelayNPC + 128.0, // fCombatDistance + 0.3, // fCombatDistanceWerewolfMod + 30.0, // fCombatForceSideAngle + 0.2, // fCombatInvisoMult + 1.5, // fCombatKODamageMult + 45.0, // fCombatTorsoSideAngle + 0.3, // fCombatTorsoStartPercent + 0.8, // fCombatTorsoStopPercent + 15.0, // fConstantEffectMult + 72.0, // fCorpseClearDelay + 72.0, // fCorpseRespawnDelay + 0.5, // fCrimeGoldDiscountMult + 0.9, // fCrimeGoldTurnInMult + 1.0, // fCrimeStealing + 0.5, // fDamageStrengthBase + 0.1, // fDamageStrengthMult + 5.0, // fDifficultyMult + 2.5, // fDiseaseXferChance + -10.0, // fDispAttacking + -1.0, // fDispBargainFailMod + 1.0, // fDispBargainSuccessMod + 0.0, // fDispCrimeMod + -10.0, // fDispDiseaseMod + 3.0, // fDispFactionMod + 1.0, // fDispFactionRankBase + 0.5, // fDispFactionRankMult + 1.0, // fDispositionMod + 50.0, // fDispPersonalityBase + 0.5, // fDispPersonalityMult + -25.0, // fDispPickPocketMod + 5.0, // fDispRaceMod + -0.5, // fDispStealing + -5.0, // fDispWeaponDrawn + 0.5, // fEffectCostMult + 0.1, // fElementalShieldMult + 3.0, // fEnchantmentChanceMult + 0.5, // fEnchantmentConstantChanceMult + 100.0, // fEnchantmentConstantDurationMult + 0.1, // fEnchantmentMult + 1000.0, // fEnchantmentValueMult + 0.3, // fEncumberedMoveEffect + 5.0, // fEncumbranceStrMult + 0.04, // fEndFatigueMult + 0.25, // fFallAcroBase + 0.01, // fFallAcroMult + 400.0, // fFallDamageDistanceMin + 0.0, // fFallDistanceBase + 0.07, // fFallDistanceMult + 2.0, // fFatigueAttackBase + 0.0, // fFatigueAttackMult + 1.25, // fFatigueBase + 4.0, // fFatigueBlockBase + 0.0, // fFatigueBlockMult + 5.0, // fFatigueJumpBase + 0.0, // fFatigueJumpMult + 0.5, // fFatigueMult + 2.5, // fFatigueReturnBase + 0.02, // fFatigueReturnMult + 5.0, // fFatigueRunBase + 2.0, // fFatigueRunMult + 1.5, // fFatigueSneakBase + 1.5, // fFatigueSneakMult + 0.0, // fFatigueSpellBase + 0.0, // fFatigueSpellCostMult + 0.0, // fFatigueSpellMult + 7.0, // fFatigueSwimRunBase + 0.0, // fFatigueSwimRunMult + 2.5, // fFatigueSwimWalkBase + 0.0, // fFatigueSwimWalkMult + 0.2, // fFightDispMult + 0.005, // fFightDistanceMultiplier + 50.0, // fFightStealing + 3000.0, // fFleeDistance + 512.0, // fGreetDistanceReset + 0.1, // fHandtoHandHealthPer + 1.0, // fHandToHandReach + 0.5, // fHoldBreathEndMult + 20.0, // fHoldBreathTime + 0.75, // fIdleChanceMultiplier + 1.0, // fIngredientMult + 0.5, // fInteriorHeadTrackMult + 128.0, // fJumpAcrobaticsBase + 4.0, // fJumpAcroMultiplier + 0.5, // fJumpEncumbranceBase + 1.0, // fJumpEncumbranceMultiplier + 0.5, // fJumpMoveBase + 0.5, // fJumpMoveMult + 1.0, // fJumpRunMultiplier + 0.5, // fKnockDownMult + 5.0, // fLevelMod + 0.1, // fLevelUpHealthEndMult + 0.6, // fLightMaxMod + 10.0, // fLuckMod + 10.0, // fMagesGuildTravel + 1.5, // fMagicCreatureCastDelay + 0.0167, // fMagicDetectRefreshRate + 1.0, // fMagicItemConstantMult + 1.0, // fMagicItemCostMult + 1.0, // fMagicItemOnceMult + 1.0, // fMagicItemPriceMult + 0.05, // fMagicItemRechargePerSecond + 1.0, // fMagicItemStrikeMult + 1.0, // fMagicItemUsedMult + 3.0, // fMagicStartIconBlink + 0.5, // fMagicSunBlockedMult + 0.75, // fMajorSkillBonus + 300.0, // fMaxFlySpeed + 0.5, // fMaxHandToHandMult + 400.0, // fMaxHeadTrackDistance + 200.0, // fMaxWalkSpeed + 300.0, // fMaxWalkSpeedCreature + 0.9, // fMedMaxMod + 0.1, // fMessageTimePerChar + 5.0, // fMinFlySpeed + 0.1, // fMinHandToHandMult + 1.0, // fMinorSkillBonus + 100.0, // fMinWalkSpeed + 5.0, // fMinWalkSpeedCreature + 1.25, // fMiscSkillBonus + 2.0, // fNPCbaseMagickaMult + 0.5, // fNPCHealthBarFade + 3.0, // fNPCHealthBarTime + 1.0, // fPCbaseMagickaMult + 0.3, // fPerDieRollMult + 5.0, // fPersonalityMod + 1.0, // fPerTempMult + -1.0, // fPickLockMult + 0.3, // fPickPocketMod + 20.0, // fPotionMinUsefulDuration + 0.5, // fPotionStrengthMult + 0.5, // fPotionT1DurMult + 1.5, // fPotionT1MagMult + 20.0, // fPotionT4BaseStrengthMult + 12.0, // fPotionT4EquipStrengthMult + 3000.0, // fProjectileMaxSpeed + 400.0, // fProjectileMinSpeed + 25.0, // fProjectileThrownStoreChance + 3.0, // fRepairAmountMult + 1.0, // fRepairMult + 1.0, // fReputationMod + 0.15, // fRestMagicMult + 0.0, // fSeriousWoundMult + 0.25, // fSleepRandMod + 0.3, // fSleepRestMod + -1.0, // fSneakBootMult + 0.5, // fSneakDistanceBase + 0.002, // fSneakDistanceMultiplier + 0.5, // fSneakNoViewMult + 1.0, // fSneakSkillMult + 0.75, // fSneakSpeedMultiplier + 1.0, // fSneakUseDelay + 500.0, // fSneakUseDist + 1.5, // fSneakViewMult + 3.0, // fSoulGemMult + 0.8, // fSpecialSkillBonus + 7.0, // fSpellMakingValueMult + 2.0, // fSpellPriceMult + 10.0, // fSpellValueMult + 0.25, // fStromWalkMult + 0.7, // fStromWindSpeed + 3.0, // fSuffocationDamage + 0.9, // fSwimHeightScale + 0.1, // fSwimRunAthleticsMult + 0.5, // fSwimRunBase + 0.02, // fSwimWalkAthleticsMult + 0.5, // fSwimWalkBase + 1.0, // fSwingBlockBase + 1.0, // fSwingBlockMult + 1000.0, // fTargetSpellMaxSpeed + 1000.0, // fThrownWeaponMaxSpeed + 300.0, // fThrownWeaponMinSpeed + 0.0, // fTrapCostMult + 4000.0, // fTravelMult + 16000.0,// fTravelTimeMult + 0.1, // fUnarmoredBase1 + 0.065, // fUnarmoredBase2 + 30.0, // fVanityDelay + 10.0, // fVoiceIdleOdds + 0.0, // fWaterReflectUpdateAlways + 10.0, // fWaterReflectUpdateSeldom + 0.1, // fWeaponDamageMult + 1.0, // fWeaponFatigueBlockMult + 0.25, // fWeaponFatigueMult + 150.0, // fWereWolfAcrobatics + 150.0, // fWereWolfAgility + 1.0, // fWereWolfAlchemy + 1.0, // fWereWolfAlteration + 1.0, // fWereWolfArmorer + 150.0, // fWereWolfAthletics + 1.0, // fWereWolfAxe + 1.0, // fWereWolfBlock + 1.0, // fWereWolfBluntWeapon + 1.0, // fWereWolfConjuration + 1.0, // fWereWolfDestruction + 1.0, // fWereWolfEnchant + 150.0, // fWereWolfEndurance + 400.0, // fWereWolfFatigue + 100.0, // fWereWolfHandtoHand + 2.0, // fWereWolfHealth + 1.0, // fWereWolfHeavyArmor + 1.0, // fWereWolfIllusion + 1.0, // fWereWolfIntellegence + 1.0, // fWereWolfLightArmor + 1.0, // fWereWolfLongBlade + 1.0, // fWereWolfLuck + 100.0, // fWereWolfMagicka + 1.0, // fWereWolfMarksman + 1.0, // fWereWolfMediumArmor + 1.0, // fWereWolfMerchantile + 1.0, // fWereWolfMysticism + 1.0, // fWereWolfPersonality + 1.0, // fWereWolfRestoration + 1.5, // fWereWolfRunMult + 1.0, // fWereWolfSecurity + 1.0, // fWereWolfShortBlade + 1.5, // fWereWolfSilverWeaponDamageMult + 1.0, // fWereWolfSneak + 1.0, // fWereWolfSpear + 1.0, // fWereWolfSpeechcraft + 150.0, // fWereWolfSpeed + 150.0, // fWereWolfStrength + 100.0, // fWereWolfUnarmored + 1.0, // fWereWolfWillPower + 15.0 // fWortChanceValue +}; + +const int CSMWorld::DefaultGMSTs::IntsDefaultValues[CSMWorld::DefaultGMSTs::IntCount] = +{ + 10, // i1stPersonSneakDelta + 50, // iAlarmAttack + 90, // iAlarmKilling + 20, // iAlarmPickPocket + 1, // iAlarmStealing + 5, // iAlarmTresspass + 2, // iAlchemyMod + 100, // iAutoPCSpellMax + 2, // iAutoRepFacMod + 0, // iAutoRepLevMod + 5, // iAutoSpellAlterationMax + 70, // iAutoSpellAttSkillMin + 2, // iAutoSpellConjurationMax + 5, // iAutoSpellDestructionMax + 5, // iAutoSpellIllusionMax + 5, // iAutoSpellMysticismMax + 5, // iAutoSpellRestorationMax + 3, // iAutoSpellTimesCanCast + -1, // iBarterFailDisposition + 1, // iBarterSuccessDisposition + 30, // iBaseArmorSkill + 50, // iBlockMaxChance + 10, // iBlockMinChance + 20, // iBootsWeight + 40, // iCrimeAttack + 1000, // iCrimeKilling + 25, // iCrimePickPocket + 1000, // iCrimeThreshold + 10, // iCrimeThresholdMultiplier + 5, // iCrimeTresspass + 30, // iCuirassWeight + 100, // iDaysinPrisonMod + -50, // iDispAttackMod + -50, // iDispKilling + -20, // iDispTresspass + 1, // iFightAlarmMult + 100, // iFightAttack + 50, // iFightAttacking + 20, // iFightDistanceBase + 50, // iFightKilling + 25, // iFightPickpocket + 25, // iFightTrespass + 0, // iFlee + 5, // iGauntletWeight + 15, // iGreavesWeight + 6, // iGreetDistanceMultiplier + 4, // iGreetDuration + 5, // iHelmWeight + 50, // iKnockDownOddsBase + 50, // iKnockDownOddsMult + 2, // iLevelUp01Mult + 2, // iLevelUp02Mult + 2, // iLevelUp03Mult + 2, // iLevelUp04Mult + 3, // iLevelUp05Mult + 3, // iLevelUp06Mult + 3, // iLevelUp07Mult + 4, // iLevelUp08Mult + 4, // iLevelUp09Mult + 5, // iLevelUp10Mult + 1, // iLevelupMajorMult + 1, // iLevelupMajorMultAttribute + 1, // iLevelupMinorMult + 1, // iLevelupMinorMultAttribute + 1, // iLevelupMiscMultAttriubte + 1, // iLevelupSpecialization + 10, // iLevelupTotal + 10, // iMagicItemChargeConst + 1, // iMagicItemChargeOnce + 10, // iMagicItemChargeStrike + 5, // iMagicItemChargeUse + 192, // iMaxActivateDist + 192, // iMaxInfoDist + 4, // iMonthsToRespawn + 1, // iNumberCreatures + 10, // iPauldronWeight + 5, // iPerMinChance + 10, // iPerMinChange + 75, // iPickMaxChance + 5, // iPickMinChance + 15, // iShieldWeight + 400, // iSoulAmountForConstantEffect + 10, // iTrainingMod + 10, // iVoiceAttackOdds + 30, // iVoiceHitOdds + 10000, // iWereWolfBounty + 100, // iWereWolfFightMod + 100, // iWereWolfFleeMod + 20 // iWereWolfLevelToAttack +}; + +const float CSMWorld::DefaultGMSTs::FloatLimits[CSMWorld::DefaultGMSTs::FloatCount * 2] = +{ + -FInf, FInf, // fAIFleeFleeMult + -FInf, FInf, // fAIFleeHealthMult + -FInf, FInf, // fAIMagicSpellMult + -FInf, FInf, // fAIMeleeArmorMult + -FInf, FInf, // fAIMeleeSummWeaponMult + -FInf, FInf, // fAIMeleeWeaponMult + -FInf, FInf, // fAIRangeMagicSpellMult + -FInf, FInf, // fAIRangeMeleeWeaponMult + 0, FInf, // fAlarmRadius + -FInf, FInf, // fAthleticsRunBonus + 0, FInf, // fAudioDefaultMaxDistance + 0, FInf, // fAudioDefaultMinDistance + 0, FInf, // fAudioMaxDistanceMult + 0, FInf, // fAudioMinDistanceMult + 0, FInf, // fAudioVoiceDefaultMaxDistance + 0, FInf, // fAudioVoiceDefaultMinDistance + 0, FInf, // fAutoPCSpellChance + 0, FInf, // fAutoSpellChance + -FInf, FInf, // fBargainOfferBase + -FInf, 0, // fBargainOfferMulti + -FInf, FInf, // fBarterGoldResetDelay + 0, FInf, // fBaseRunMultiplier + -FInf, FInf, // fBlockStillBonus + 0, FInf, // fBribe1000Mod + 0, FInf, // fBribe100Mod + 0, FInf, // fBribe10Mod + 0, FInf, // fCombatAngleXY + 0, FInf, // fCombatAngleZ + 0, 1, // fCombatArmorMinMult + -180, 0, // fCombatBlockLeftAngle + 0, 180, // fCombatBlockRightAngle + 0, FInf, // fCombatCriticalStrikeMult + 0, FInf, // fCombatDelayCreature + 0, FInf, // fCombatDelayNPC + 0, FInf, // fCombatDistance + -FInf, FInf, // fCombatDistanceWerewolfMod + -FInf, FInf, // fCombatForceSideAngle + 0, FInf, // fCombatInvisoMult + 0, FInf, // fCombatKODamageMult + -FInf, FInf, // fCombatTorsoSideAngle + -FInf, FInf, // fCombatTorsoStartPercent + -FInf, FInf, // fCombatTorsoStopPercent + -FInf, FInf, // fConstantEffectMult + -FInf, FInf, // fCorpseClearDelay + -FInf, FInf, // fCorpseRespawnDelay + 0, 1, // fCrimeGoldDiscountMult + 0, FInf, // fCrimeGoldTurnInMult + 0, FInf, // fCrimeStealing + 0, FInf, // fDamageStrengthBase + 0, FInf, // fDamageStrengthMult + -FInf, FInf, // fDifficultyMult + 0, FInf, // fDiseaseXferChance + -FInf, 0, // fDispAttacking + -FInf, FInf, // fDispBargainFailMod + -FInf, FInf, // fDispBargainSuccessMod + -FInf, 0, // fDispCrimeMod + -FInf, 0, // fDispDiseaseMod + 0, FInf, // fDispFactionMod + 0, FInf, // fDispFactionRankBase + 0, FInf, // fDispFactionRankMult + 0, FInf, // fDispositionMod + 0, FInf, // fDispPersonalityBase + 0, FInf, // fDispPersonalityMult + -FInf, 0, // fDispPickPocketMod + 0, FInf, // fDispRaceMod + -FInf, 0, // fDispStealing + -FInf, 0, // fDispWeaponDrawn + 0, FInf, // fEffectCostMult + 0, FInf, // fElementalShieldMult + FEps, FInf, // fEnchantmentChanceMult + 0, FInf, // fEnchantmentConstantChanceMult + 0, FInf, // fEnchantmentConstantDurationMult + 0, FInf, // fEnchantmentMult + 0, FInf, // fEnchantmentValueMult + 0, FInf, // fEncumberedMoveEffect + 0, FInf, // fEncumbranceStrMult + 0, FInf, // fEndFatigueMult + -FInf, FInf, // fFallAcroBase + 0, FInf, // fFallAcroMult + 0, FInf, // fFallDamageDistanceMin + -FInf, FInf, // fFallDistanceBase + 0, FInf, // fFallDistanceMult + -FInf, FInf, // fFatigueAttackBase + 0, FInf, // fFatigueAttackMult + 0, FInf, // fFatigueBase + 0, FInf, // fFatigueBlockBase + 0, FInf, // fFatigueBlockMult + 0, FInf, // fFatigueJumpBase + 0, FInf, // fFatigueJumpMult + 0, FInf, // fFatigueMult + -FInf, FInf, // fFatigueReturnBase + 0, FInf, // fFatigueReturnMult + -FInf, FInf, // fFatigueRunBase + 0, FInf, // fFatigueRunMult + -FInf, FInf, // fFatigueSneakBase + 0, FInf, // fFatigueSneakMult + -FInf, FInf, // fFatigueSpellBase + -FInf, FInf, // fFatigueSpellCostMult + 0, FInf, // fFatigueSpellMult + -FInf, FInf, // fFatigueSwimRunBase + 0, FInf, // fFatigueSwimRunMult + -FInf, FInf, // fFatigueSwimWalkBase + 0, FInf, // fFatigueSwimWalkMult + -FInf, FInf, // fFightDispMult + -FInf, FInf, // fFightDistanceMultiplier + -FInf, FInf, // fFightStealing + -FInf, FInf, // fFleeDistance + -FInf, FInf, // fGreetDistanceReset + 0, FInf, // fHandtoHandHealthPer + 0, FInf, // fHandToHandReach + -FInf, FInf, // fHoldBreathEndMult + 0, FInf, // fHoldBreathTime + 0, FInf, // fIdleChanceMultiplier + -FInf, FInf, // fIngredientMult + 0, FInf, // fInteriorHeadTrackMult + -FInf, FInf, // fJumpAcrobaticsBase + 0, FInf, // fJumpAcroMultiplier + -FInf, FInf, // fJumpEncumbranceBase + 0, FInf, // fJumpEncumbranceMultiplier + -FInf, FInf, // fJumpMoveBase + 0, FInf, // fJumpMoveMult + 0, FInf, // fJumpRunMultiplier + -FInf, FInf, // fKnockDownMult + 0, FInf, // fLevelMod + 0, FInf, // fLevelUpHealthEndMult + 0, FInf, // fLightMaxMod + 0, FInf, // fLuckMod + 0, FInf, // fMagesGuildTravel + -FInf, FInf, // fMagicCreatureCastDelay + -FInf, FInf, // fMagicDetectRefreshRate + -FInf, FInf, // fMagicItemConstantMult + -FInf, FInf, // fMagicItemCostMult + -FInf, FInf, // fMagicItemOnceMult + -FInf, FInf, // fMagicItemPriceMult + 0, FInf, // fMagicItemRechargePerSecond + -FInf, FInf, // fMagicItemStrikeMult + -FInf, FInf, // fMagicItemUsedMult + 0, FInf, // fMagicStartIconBlink + 0, FInf, // fMagicSunBlockedMult + FEps, FInf, // fMajorSkillBonus + 0, FInf, // fMaxFlySpeed + 0, FInf, // fMaxHandToHandMult + 0, FInf, // fMaxHeadTrackDistance + 0, FInf, // fMaxWalkSpeed + 0, FInf, // fMaxWalkSpeedCreature + 0, FInf, // fMedMaxMod + 0, FInf, // fMessageTimePerChar + 0, FInf, // fMinFlySpeed + 0, FInf, // fMinHandToHandMult + FEps, FInf, // fMinorSkillBonus + 0, FInf, // fMinWalkSpeed + 0, FInf, // fMinWalkSpeedCreature + FEps, FInf, // fMiscSkillBonus + 0, FInf, // fNPCbaseMagickaMult + 0, FInf, // fNPCHealthBarFade + 0, FInf, // fNPCHealthBarTime + 0, FInf, // fPCbaseMagickaMult + 0, FInf, // fPerDieRollMult + 0, FInf, // fPersonalityMod + 0, FInf, // fPerTempMult + -FInf, 0, // fPickLockMult + 0, FInf, // fPickPocketMod + -FInf, FInf, // fPotionMinUsefulDuration + 0, FInf, // fPotionStrengthMult + FEps, FInf, // fPotionT1DurMult + FEps, FInf, // fPotionT1MagMult + -FInf, FInf, // fPotionT4BaseStrengthMult + -FInf, FInf, // fPotionT4EquipStrengthMult + 0, FInf, // fProjectileMaxSpeed + 0, FInf, // fProjectileMinSpeed + 0, FInf, // fProjectileThrownStoreChance + 0, FInf, // fRepairAmountMult + 0, FInf, // fRepairMult + 0, FInf, // fReputationMod + 0, FInf, // fRestMagicMult + -FInf, FInf, // fSeriousWoundMult + 0, FInf, // fSleepRandMod + 0, FInf, // fSleepRestMod + -FInf, 0, // fSneakBootMult + -FInf, FInf, // fSneakDistanceBase + 0, FInf, // fSneakDistanceMultiplier + 0, FInf, // fSneakNoViewMult + 0, FInf, // fSneakSkillMult + 0, FInf, // fSneakSpeedMultiplier + 0, FInf, // fSneakUseDelay + 0, FInf, // fSneakUseDist + 0, FInf, // fSneakViewMult + 0, FInf, // fSoulGemMult + 0, FInf, // fSpecialSkillBonus + 0, FInf, // fSpellMakingValueMult + -FInf, FInf, // fSpellPriceMult + 0, FInf, // fSpellValueMult + 0, FInf, // fStromWalkMult + 0, FInf, // fStromWindSpeed + 0, FInf, // fSuffocationDamage + 0, FInf, // fSwimHeightScale + 0, FInf, // fSwimRunAthleticsMult + 0, FInf, // fSwimRunBase + -FInf, FInf, // fSwimWalkAthleticsMult + -FInf, FInf, // fSwimWalkBase + 0, FInf, // fSwingBlockBase + 0, FInf, // fSwingBlockMult + 0, FInf, // fTargetSpellMaxSpeed + 0, FInf, // fThrownWeaponMaxSpeed + 0, FInf, // fThrownWeaponMinSpeed + 0, FInf, // fTrapCostMult + 0, FInf, // fTravelMult + 0, FInf, // fTravelTimeMult + 0, FInf, // fUnarmoredBase1 + 0, FInf, // fUnarmoredBase2 + 0, FInf, // fVanityDelay + 0, FInf, // fVoiceIdleOdds + -FInf, FInf, // fWaterReflectUpdateAlways + -FInf, FInf, // fWaterReflectUpdateSeldom + 0, FInf, // fWeaponDamageMult + 0, FInf, // fWeaponFatigueBlockMult + 0, FInf, // fWeaponFatigueMult + 0, FInf, // fWereWolfAcrobatics + -FInf, FInf, // fWereWolfAgility + -FInf, FInf, // fWereWolfAlchemy + -FInf, FInf, // fWereWolfAlteration + -FInf, FInf, // fWereWolfArmorer + -FInf, FInf, // fWereWolfAthletics + -FInf, FInf, // fWereWolfAxe + -FInf, FInf, // fWereWolfBlock + -FInf, FInf, // fWereWolfBluntWeapon + -FInf, FInf, // fWereWolfConjuration + -FInf, FInf, // fWereWolfDestruction + -FInf, FInf, // fWereWolfEnchant + -FInf, FInf, // fWereWolfEndurance + -FInf, FInf, // fWereWolfFatigue + -FInf, FInf, // fWereWolfHandtoHand + -FInf, FInf, // fWereWolfHealth + -FInf, FInf, // fWereWolfHeavyArmor + -FInf, FInf, // fWereWolfIllusion + -FInf, FInf, // fWereWolfIntellegence + -FInf, FInf, // fWereWolfLightArmor + -FInf, FInf, // fWereWolfLongBlade + -FInf, FInf, // fWereWolfLuck + -FInf, FInf, // fWereWolfMagicka + -FInf, FInf, // fWereWolfMarksman + -FInf, FInf, // fWereWolfMediumArmor + -FInf, FInf, // fWereWolfMerchantile + -FInf, FInf, // fWereWolfMysticism + -FInf, FInf, // fWereWolfPersonality + -FInf, FInf, // fWereWolfRestoration + 0, FInf, // fWereWolfRunMult + -FInf, FInf, // fWereWolfSecurity + -FInf, FInf, // fWereWolfShortBlade + -FInf, FInf, // fWereWolfSilverWeaponDamageMult + -FInf, FInf, // fWereWolfSneak + -FInf, FInf, // fWereWolfSpear + -FInf, FInf, // fWereWolfSpeechcraft + -FInf, FInf, // fWereWolfSpeed + -FInf, FInf, // fWereWolfStrength + -FInf, FInf, // fWereWolfUnarmored + -FInf, FInf, // fWereWolfWillPower + 0, FInf // fWortChanceValue +}; + +const int CSMWorld::DefaultGMSTs::IntLimits[CSMWorld::DefaultGMSTs::IntCount * 2] = +{ + IMin, IMax, // i1stPersonSneakDelta + IMin, IMax, // iAlarmAttack + IMin, IMax, // iAlarmKilling + IMin, IMax, // iAlarmPickPocket + IMin, IMax, // iAlarmStealing + IMin, IMax, // iAlarmTresspass + IMin, IMax, // iAlchemyMod + 0, IMax, // iAutoPCSpellMax + IMin, IMax, // iAutoRepFacMod + IMin, IMax, // iAutoRepLevMod + IMin, IMax, // iAutoSpellAlterationMax + 0, IMax, // iAutoSpellAttSkillMin + IMin, IMax, // iAutoSpellConjurationMax + IMin, IMax, // iAutoSpellDestructionMax + IMin, IMax, // iAutoSpellIllusionMax + IMin, IMax, // iAutoSpellMysticismMax + IMin, IMax, // iAutoSpellRestorationMax + 0, IMax, // iAutoSpellTimesCanCast + IMin, 0, // iBarterFailDisposition + 0, IMax, // iBarterSuccessDisposition + 1, IMax, // iBaseArmorSkill + 0, IMax, // iBlockMaxChance + 0, IMax, // iBlockMinChance + 0, IMax, // iBootsWeight + IMin, IMax, // iCrimeAttack + IMin, IMax, // iCrimeKilling + IMin, IMax, // iCrimePickPocket + 0, IMax, // iCrimeThreshold + 0, IMax, // iCrimeThresholdMultiplier + IMin, IMax, // iCrimeTresspass + 0, IMax, // iCuirassWeight + 1, IMax, // iDaysinPrisonMod + IMin, 0, // iDispAttackMod + IMin, 0, // iDispKilling + IMin, 0, // iDispTresspass + IMin, IMax, // iFightAlarmMult + IMin, IMax, // iFightAttack + IMin, IMax, // iFightAttacking + 0, IMax, // iFightDistanceBase + IMin, IMax, // iFightKilling + IMin, IMax, // iFightPickpocket + IMin, IMax, // iFightTrespass + IMin, IMax, // iFlee + 0, IMax, // iGauntletWeight + 0, IMax, // iGreavesWeight + 0, IMax, // iGreetDistanceMultiplier + 0, IMax, // iGreetDuration + 0, IMax, // iHelmWeight + IMin, IMax, // iKnockDownOddsBase + IMin, IMax, // iKnockDownOddsMult + IMin, IMax, // iLevelUp01Mult + IMin, IMax, // iLevelUp02Mult + IMin, IMax, // iLevelUp03Mult + IMin, IMax, // iLevelUp04Mult + IMin, IMax, // iLevelUp05Mult + IMin, IMax, // iLevelUp06Mult + IMin, IMax, // iLevelUp07Mult + IMin, IMax, // iLevelUp08Mult + IMin, IMax, // iLevelUp09Mult + IMin, IMax, // iLevelUp10Mult + IMin, IMax, // iLevelupMajorMult + IMin, IMax, // iLevelupMajorMultAttribute + IMin, IMax, // iLevelupMinorMult + IMin, IMax, // iLevelupMinorMultAttribute + IMin, IMax, // iLevelupMiscMultAttriubte + IMin, IMax, // iLevelupSpecialization + IMin, IMax, // iLevelupTotal + IMin, IMax, // iMagicItemChargeConst + IMin, IMax, // iMagicItemChargeOnce + IMin, IMax, // iMagicItemChargeStrike + IMin, IMax, // iMagicItemChargeUse + IMin, IMax, // iMaxActivateDist + IMin, IMax, // iMaxInfoDist + 0, IMax, // iMonthsToRespawn + 0, IMax, // iNumberCreatures + 0, IMax, // iPauldronWeight + 0, IMax, // iPerMinChance + 0, IMax, // iPerMinChange + 0, IMax, // iPickMaxChance + 0, IMax, // iPickMinChance + 0, IMax, // iShieldWeight + 0, IMax, // iSoulAmountForConstantEffect + 0, IMax, // iTrainingMod + 0, IMax, // iVoiceAttackOdds + 0, IMax, // iVoiceHitOdds + IMin, IMax, // iWereWolfBounty + IMin, IMax, // iWereWolfFightMod + IMin, IMax, // iWereWolfFleeMod + IMin, IMax // iWereWolfLevelToAttack +}; diff --git a/apps/opencs/model/world/defaultgmsts.hpp b/apps/opencs/model/world/defaultgmsts.hpp new file mode 100644 index 0000000000..ba65be805a --- /dev/null +++ b/apps/opencs/model/world/defaultgmsts.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_WORLD_DEFAULTGMSTS_H +#define CSM_WORLD_DEFAULTGMSTS_H + +#include + +namespace CSMWorld { + namespace DefaultGMSTs { + + const size_t FloatCount = 258; + const size_t IntCount = 89; + const size_t StringCount = 1174; + + const size_t OptionalFloatCount = 42; + const size_t OptionalIntCount = 4; + const size_t OptionalStringCount = 26; + + extern const char* Floats[]; + extern const char * Ints[]; + extern const char * Strings[]; + + extern const char * OptionalFloats[]; + extern const char * OptionalInts[]; + extern const char * OptionalStrings[]; + + extern const float FloatsDefaultValues[]; + extern const int IntsDefaultValues[]; + + extern const float FloatLimits[]; + extern const int IntLimits[]; + + } +} + +#endif From bbda5fe634aafc3104a47ee02d0f0bc236274cba Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 18 Jan 2016 12:34:33 -0500 Subject: [PATCH 110/765] Removed hints from error messages and fixed naming to match conventions --- apps/opencs/model/doc/document.cpp | 28 ++++----- apps/opencs/model/tools/gmstcheck.cpp | 78 +++++++++++++++++------- apps/opencs/model/tools/gmstcheck.hpp | 6 +- apps/opencs/model/tools/tools.cpp | 2 +- apps/opencs/model/world/defaultgmsts.cpp | 20 +++--- apps/opencs/model/world/defaultgmsts.hpp | 2 +- 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index ea1fbb3305..19891363fa 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -14,28 +14,28 @@ void CSMDoc::Document::addGmsts() { - for (size_t i=0; i < CSMWorld::DefaultGMSTs::FloatCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::Floats[i]; + gmst.mId = CSMWorld::DefaultGmsts::Floats[i]; gmst.mValue.setType (ESM::VT_Float); - gmst.mValue.setFloat (CSMWorld::DefaultGMSTs::FloatsDefaultValues[i]); + gmst.mValue.setFloat (CSMWorld::DefaultGmsts::FloatsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (size_t i=0; i < CSMWorld::DefaultGMSTs::IntCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::Ints[i]; + gmst.mId = CSMWorld::DefaultGmsts::Ints[i]; gmst.mValue.setType (ESM::VT_Int); - gmst.mValue.setInteger (CSMWorld::DefaultGMSTs::IntsDefaultValues[i]); + gmst.mValue.setInteger (CSMWorld::DefaultGmsts::IntsDefaultValues[i]); getData().getGmsts().add (gmst); } - for (size_t i=0; i < CSMWorld::DefaultGMSTs::StringCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::Strings[i]; + gmst.mId = CSMWorld::DefaultGmsts::Strings[i]; gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); getData().getGmsts().add (gmst); @@ -44,28 +44,28 @@ void CSMDoc::Document::addGmsts() void CSMDoc::Document::addOptionalGmsts() { - for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalFloatCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalFloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::OptionalFloats[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalFloats[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Float); addOptionalGmst (gmst); } - for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalIntCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalIntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::OptionalInts[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalInts[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Int); addOptionalGmst (gmst); } - for (size_t i=0; i < CSMWorld::DefaultGMSTs::OptionalStringCount; ++i) + for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalStringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGMSTs::OptionalStrings[i]; + gmst.mId = CSMWorld::DefaultGmsts::OptionalStrings[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); diff --git a/apps/opencs/model/tools/gmstcheck.cpp b/apps/opencs/model/tools/gmstcheck.cpp index 6c17ff3a5a..0c32c00564 100644 --- a/apps/opencs/model/tools/gmstcheck.cpp +++ b/apps/opencs/model/tools/gmstcheck.cpp @@ -1,17 +1,19 @@ #include "gmstcheck.hpp" +#include + #include "../world/defaultgmsts.hpp" -CSMTools::GMSTCheckStage::GMSTCheckStage(const CSMWorld::IdCollection& gameSettings) +CSMTools::GmstCheckStage::GmstCheckStage(const CSMWorld::IdCollection& gameSettings) : mGameSettings(gameSettings) {} -int CSMTools::GMSTCheckStage::setup() +int CSMTools::GmstCheckStage::setup() { return mGameSettings.getSize(); } -void CSMTools::GMSTCheckStage::perform(int stage, CSMDoc::Messages& messages) +void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mGameSettings.getRecord (stage); @@ -30,20 +32,25 @@ void CSMTools::GMSTCheckStage::perform(int stage, CSMDoc::Messages& messages) // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) if (gmst.mId[0] == 'f') { - for (size_t i = 0; i < CSMWorld::DefaultGMSTs::FloatCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { - if (gmst.mId == CSMWorld::DefaultGMSTs::Floats[i]) + if (gmst.mId == CSMWorld::DefaultGmsts::Floats[i]) { if (gmst.mValue.getType() != ESM::VT_Float) - messages.add(id, "The type of " + gmst.mId + " is incorrect", - "Change the GMST type to a float", CSMDoc::Message::Severity_Error); + { + std::ostringstream stream; + stream << "Expected float type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } - if (gmst.mValue.getFloat() < CSMWorld::DefaultGMSTs::FloatLimits[i*2]) - messages.add(id, gmst.mId + " is less than the suggested range", "Change the value", + if (gmst.mValue.getFloat() < CSMWorld::DefaultGmsts::FloatLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); - if (gmst.mValue.getFloat() > CSMWorld::DefaultGMSTs::FloatLimits[i*2+1]) - messages.add(id, gmst.mId + " is more than the suggested range", "Change the value", + if (gmst.mValue.getFloat() > CSMWorld::DefaultGmsts::FloatLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); break; // for loop @@ -52,20 +59,25 @@ void CSMTools::GMSTCheckStage::perform(int stage, CSMDoc::Messages& messages) } else if (gmst.mId[0] == 'i') { - for (size_t i = 0; i < CSMWorld::DefaultGMSTs::IntCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { - if (gmst.mId == CSMWorld::DefaultGMSTs::Ints[i]) + if (gmst.mId == CSMWorld::DefaultGmsts::Ints[i]) { if (gmst.mValue.getType() != ESM::VT_Int) - messages.add(id, "The type of " + gmst.mId + " is incorrect", - "Change the GMST type to an int", CSMDoc::Message::Severity_Error); + { + std::ostringstream stream; + stream << "Expected int type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } - if (gmst.mValue.getInteger() < CSMWorld::DefaultGMSTs::IntLimits[i*2]) - messages.add(id, gmst.mId + " is less than the suggested range", "Change the value", + if (gmst.mValue.getInteger() < CSMWorld::DefaultGmsts::IntLimits[i*2]) + messages.add(id, gmst.mId + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); - if (gmst.mValue.getInteger() > CSMWorld::DefaultGMSTs::IntLimits[i*2+1]) - messages.add(id, gmst.mId + " is more than the suggested range", "Change the value", + if (gmst.mValue.getInteger() > CSMWorld::DefaultGmsts::IntLimits[i*2+1]) + messages.add(id, gmst.mId + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); break; // for loop @@ -74,18 +86,38 @@ void CSMTools::GMSTCheckStage::perform(int stage, CSMDoc::Messages& messages) } else if (gmst.mId[0] == 's') { - for (size_t i = 0; i < CSMWorld::DefaultGMSTs::StringCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { - if (gmst.mId == CSMWorld::DefaultGMSTs::Strings[i]) + if (gmst.mId == CSMWorld::DefaultGmsts::Strings[i]) { ESM::VarType type = gmst.mValue.getType(); if (type != ESM::VT_String && type != ESM::VT_None) - messages.add(id, "The type of " + gmst.mId + " is incorrect", - "Change the GMST type to a string", CSMDoc::Message::Severity_Error); + { + std::ostringstream stream; + stream << "Expected string or none type for " << gmst.mId << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } break; // for loop } } } } + +std::string CSMTools::GmstCheckStage::varTypeToString(ESM::VarType type) +{ + switch (type) + { + case ESM::VT_Unknown: return "unknown"; + case ESM::VT_None: return "none"; + case ESM::VT_Short: return "short"; + case ESM::VT_Int: return "int"; + case ESM::VT_Long: return "long"; + case ESM::VT_Float: return "float"; + case ESM::VT_String: return "string"; + default: return "unhandled"; + } +} diff --git a/apps/opencs/model/tools/gmstcheck.hpp b/apps/opencs/model/tools/gmstcheck.hpp index ca1564d9e8..0d4f7f2046 100644 --- a/apps/opencs/model/tools/gmstcheck.hpp +++ b/apps/opencs/model/tools/gmstcheck.hpp @@ -10,11 +10,11 @@ namespace CSMTools { /// \brief VerifyStage: make sure that GMSTs are alright - class GMSTCheckStage : public CSMDoc::Stage + class GmstCheckStage : public CSMDoc::Stage { public: - GMSTCheckStage(const CSMWorld::IdCollection& gameSettings); + GmstCheckStage(const CSMWorld::IdCollection& gameSettings); virtual int setup(); ///< \return number of steps @@ -26,6 +26,8 @@ namespace CSMTools const CSMWorld::IdCollection& mGameSettings; + std::string varTypeToString(ESM::VarType); + }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 9f36193ef1..e750092b93 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -112,7 +112,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - mVerifierOperation->appendStage (new GMSTCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/world/defaultgmsts.cpp b/apps/opencs/model/world/defaultgmsts.cpp index ef3d536b1e..f44e98411c 100644 --- a/apps/opencs/model/world/defaultgmsts.cpp +++ b/apps/opencs/model/world/defaultgmsts.cpp @@ -8,7 +8,7 @@ const float FEps = std::numeric_limits::epsilon(); const int IMax = std::numeric_limits::max(); const int IMin = std::numeric_limits::min(); -const char* CSMWorld::DefaultGMSTs::Floats[CSMWorld::DefaultGMSTs::FloatCount] = +const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = { "fAIFleeFleeMult", "fAIFleeHealthMult", @@ -270,7 +270,7 @@ const char* CSMWorld::DefaultGMSTs::Floats[CSMWorld::DefaultGMSTs::FloatCount] = "fWortChanceValue" }; -const char * CSMWorld::DefaultGMSTs::Ints[CSMWorld::DefaultGMSTs::IntCount] = +const char * CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = { "i1stPersonSneakDelta", "iAlarmAttack", @@ -363,7 +363,7 @@ const char * CSMWorld::DefaultGMSTs::Ints[CSMWorld::DefaultGMSTs::IntCount] = "iWereWolfLevelToAttack" }; -const char * CSMWorld::DefaultGMSTs::Strings[CSMWorld::DefaultGMSTs::StringCount] = +const char * CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount] = { "s3dAudio", "s3dHardware", @@ -1541,7 +1541,7 @@ const char * CSMWorld::DefaultGMSTs::Strings[CSMWorld::DefaultGMSTs::StringCount "sYourGold" }; -const char * CSMWorld::DefaultGMSTs::OptionalFloats[CSMWorld::DefaultGMSTs::OptionalFloatCount] = +const char * CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::OptionalFloatCount] = { "fCombatDistanceWerewolfMod", "fFleeDistance", @@ -1587,7 +1587,7 @@ const char * CSMWorld::DefaultGMSTs::OptionalFloats[CSMWorld::DefaultGMSTs::Opti "fWereWolfWillPower" }; -const char * CSMWorld::DefaultGMSTs::OptionalInts[CSMWorld::DefaultGMSTs::OptionalIntCount] = +const char * CSMWorld::DefaultGmsts::OptionalInts[CSMWorld::DefaultGmsts::OptionalIntCount] = { "iWereWolfBounty", "iWereWolfFightMod", @@ -1595,7 +1595,7 @@ const char * CSMWorld::DefaultGMSTs::OptionalInts[CSMWorld::DefaultGMSTs::Option "iWereWolfLevelToAttack" }; -const char * CSMWorld::DefaultGMSTs::OptionalStrings[CSMWorld::DefaultGMSTs::OptionalStringCount] = +const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::OptionalStringCount] = { "sCompanionShare", "sCompanionWarningButtonOne", @@ -1625,7 +1625,7 @@ const char * CSMWorld::DefaultGMSTs::OptionalStrings[CSMWorld::DefaultGMSTs::Opt "sWerewolfRestMessage" }; -const float CSMWorld::DefaultGMSTs::FloatsDefaultValues[CSMWorld::DefaultGMSTs::FloatCount] = +const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = { 0.3, // fAIFleeFleeMult 7.0, // fAIFleeHealthMult @@ -1887,7 +1887,7 @@ const float CSMWorld::DefaultGMSTs::FloatsDefaultValues[CSMWorld::DefaultGMSTs:: 15.0 // fWortChanceValue }; -const int CSMWorld::DefaultGMSTs::IntsDefaultValues[CSMWorld::DefaultGMSTs::IntCount] = +const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = { 10, // i1stPersonSneakDelta 50, // iAlarmAttack @@ -1980,7 +1980,7 @@ const int CSMWorld::DefaultGMSTs::IntsDefaultValues[CSMWorld::DefaultGMSTs::IntC 20 // iWereWolfLevelToAttack }; -const float CSMWorld::DefaultGMSTs::FloatLimits[CSMWorld::DefaultGMSTs::FloatCount * 2] = +const float CSMWorld::DefaultGmsts::FloatLimits[CSMWorld::DefaultGmsts::FloatCount * 2] = { -FInf, FInf, // fAIFleeFleeMult -FInf, FInf, // fAIFleeHealthMult @@ -2242,7 +2242,7 @@ const float CSMWorld::DefaultGMSTs::FloatLimits[CSMWorld::DefaultGMSTs::FloatCou 0, FInf // fWortChanceValue }; -const int CSMWorld::DefaultGMSTs::IntLimits[CSMWorld::DefaultGMSTs::IntCount * 2] = +const int CSMWorld::DefaultGmsts::IntLimits[CSMWorld::DefaultGmsts::IntCount * 2] = { IMin, IMax, // i1stPersonSneakDelta IMin, IMax, // iAlarmAttack diff --git a/apps/opencs/model/world/defaultgmsts.hpp b/apps/opencs/model/world/defaultgmsts.hpp index ba65be805a..a2888ed6ac 100644 --- a/apps/opencs/model/world/defaultgmsts.hpp +++ b/apps/opencs/model/world/defaultgmsts.hpp @@ -4,7 +4,7 @@ #include namespace CSMWorld { - namespace DefaultGMSTs { + namespace DefaultGmsts { const size_t FloatCount = 258; const size_t IntCount = 89; From c82d9a1e873feae70a845feae26b6cbb5572de79 Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Mon, 18 Jan 2016 19:56:35 -0600 Subject: [PATCH 111/765] Adjust ContainerStore / InventoryStore to allow partial unequip of items. --- apps/openmw/mwworld/containerstore.cpp | 12 +++++++----- apps/openmw/mwworld/containerstore.hpp | 6 ++++-- apps/openmw/mwworld/inventorystore.cpp | 27 ++++++++++++++++++++++++++ apps/openmw/mwworld/inventorystore.hpp | 9 +++++++++ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 45390405e0..e07ba433ac 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -136,16 +136,18 @@ int MWWorld::ContainerStore::count(const std::string &id) return total; } -void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) { - if (ptr.getRefData().getCount() <= 1) - return; - MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-1); + if (ptr.getRefData().getCount() <= count) + return end(); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count); const std::string script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); - remove(ptr, ptr.getRefData().getCount()-1, container); + remove(ptr, ptr.getRefData().getCount()-count, container); + + return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index b7ec4bb74e..6a8fc47616 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -130,8 +130,10 @@ namespace MWWorld /// /// @return the number of items actually removed - void unstack (const Ptr& ptr, const Ptr& container); - ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1); + ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count). + /// + /// @return an iterator to the new stack, or end() if no new stack was created. MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); ///< Attempt to re-stack an item in this container. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index dbd2c67940..2c994f1467 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -574,6 +574,33 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(const Ptr& item, const Ptr& actor, int count) +{ + if (!isEquipped(item)) + throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); + if (count <= 0) + throw std::runtime_error ("attempt to unequip nothing (count <= 0)"); + if (count > item.getRefData().getCount()) + throw std::runtime_error ("attempt to unequip more items than equipped"); + + if (count == item.getRefData().getCount()) + return unequipItem(item, actor); + + // Move items to an existing stack if possible, otherwise split count items out into a new stack. + // Moving counts manually here, since ContainerStore's restack can't target unequipped stacks. + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, item) && !isEquipped(*iter)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + count); + item.getRefData().setCount(item.getRefData().getCount() - count); + return iter; + } + } + + return unstack(item, actor, item.getRefData().getCount() - count); +} + MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getListener() { return mListener; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 28c44bcec1..2d7c9f6e9c 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -188,6 +188,15 @@ namespace MWWorld /// (it can be re-stacked so its count may be different than when it /// was equipped). + ContainerStoreIterator unequipItemQuantity(const Ptr& item, const Ptr& actor, int count); + ///< Unequip a specific quantity of an item identified by its Ptr. + /// An exception is thrown if the item is not currently equipped, + /// if count <= 0, or if count > the item stack size. + /// + /// @return an iterator to the unequipped items that were previously + /// in the slot (they can be re-stacked so its count may be different + /// than the requested count). + void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener From 1ff49cc637134e75e1101bbd14bcbffe2f4d3b24 Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Mon, 18 Jan 2016 19:58:19 -0600 Subject: [PATCH 112/765] Improve Drop command behavior. (Fixes #1544) --- apps/openmw/mwscript/miscextensions.cpp | 35 +++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 593fdcca52..51f0c6c55b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -21,6 +21,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -509,13 +510,43 @@ namespace MWScript if (amount == 0) return; - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); + // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. + MWWorld::InventoryStore *invStorePtr = 0; + if (ptr.getClass().hasInventoryStore(ptr)) { + invStorePtr = &ptr.getClass().getInventoryStore(ptr); + int numNotEquipped = invStorePtr->count(item); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStorePtr->getSlot (slot); + if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + { + numNotEquipped -= it->getRefData().getCount(); + } + } + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) + { + MWWorld::ContainerStoreIterator it = invStorePtr->getSlot (slot); + if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + { + int numToRemove = it->getRefData().getCount(); + if (numToRemove > amount - numNotEquipped) + { + numToRemove = amount - numNotEquipped; + } + invStorePtr->unequipItemQuantity(*it, ptr, numToRemove); + numNotEquipped += numToRemove; + } + } + } int toRemove = amount; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) + && (!invStorePtr || !invStorePtr->isEquipped(*iter))) { int removed = store.remove(*iter, toRemove, ptr); MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); From 5699cf7f0988d904fe4170124c9aec4faa729df7 Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Mon, 18 Jan 2016 20:00:18 -0600 Subject: [PATCH 113/765] Barter: Leave unsold projectiles equipped. --- apps/openmw/mwgui/inventorywindow.cpp | 10 ++++++---- apps/openmw/mwgui/inventorywindow.hpp | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 7678cb006c..facb17d66c 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -265,18 +265,20 @@ namespace MWGui } } - void InventoryWindow::ensureSelectedItemUnequipped() + void InventoryWindow::ensureSelectedItemUnequipped(int count) { const ItemStack& item = mTradeModel->getItem(mSelectedItem); if (item.mType == ItemStack::Type_Equipped) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); + MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, mPtr, count); // The unequipped item was re-stacked. We have to update the index // since the item pointed does not exist anymore. if (item.mBase != newStack) { + updateItemView(); // Unequipping can produce a new stack, not yet in the window... + // newIndex will store the index of the ItemStack the item was stacked on int newIndex = -1; for (size_t i=0; i < mTradeModel->getItemCount(); ++i) @@ -298,14 +300,14 @@ namespace MWGui void InventoryWindow::dragItem(MyGUI::Widget* sender, int count) { - ensureSelectedItemUnequipped(); + ensureSelectedItemUnequipped(count); mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); notifyContentChanged(); } void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) { - ensureSelectedItemUnequipped(); + ensureSelectedItemUnequipped(count); const ItemStack& item = mTradeModel->getItem(mSelectedItem); std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index a8a1b15a13..b7ae067ace 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -126,8 +126,8 @@ namespace MWGui void adjustPanes(); - /// Unequips mSelectedItem, if it is equipped, and then updates mSelectedItem in case it was re-stacked - void ensureSelectedItemUnequipped(); + /// Unequips count items from mSelectedItem, if it is equipped, and then updates mSelectedItem in case the items were re-stacked + void ensureSelectedItemUnequipped(int count); }; } From 19b39de351d9329a10dc42e2906e919018100661 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 19 Jan 2016 11:15:36 +0100 Subject: [PATCH 114/765] use trusty --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0910ac5462..10b47ee7dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ os: - linux # - osx language: cpp +sudo: required +dist: trusty branches: only: - master From 0305ae369327ad39a354a5b7f91abaed424b0287 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 19 Jan 2016 12:17:13 +0100 Subject: [PATCH 115/765] added selection mode UI --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/instancemode.cpp | 23 +++++++++++++--- apps/opencs/view/render/instancemode.hpp | 3 +++ .../view/render/instanceselectionmode.cpp | 27 +++++++++++++++++++ .../view/render/instanceselectionmode.hpp | 19 +++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 apps/opencs/view/render/instanceselectionmode.cpp create mode 100644 apps/opencs/view/render/instanceselectionmode.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0e9a49432b..ae4bbd66cf 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -85,7 +85,7 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode instancemode + previewwidget editmode instancemode instanceselectionmode ) opencs_units_noqt (view/render diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 449d9d7a6f..53ebc70525 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -16,10 +16,11 @@ #include "object.hpp" #include "worldspacewidget.hpp" #include "pagedworldspacewidget.hpp" +#include "instanceselectionmode.hpp" CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", - parent), mSubMode (0) + parent), mSubMode (0), mSelectionMode (0) { } @@ -48,16 +49,30 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) "Not implemented yet"); } + if (!mSelectionMode) + mSelectionMode = new InstanceSelectionMode (toolbar); + EditMode::activate (toolbar); toolbar->addTool (mSubMode); + toolbar->addTool (mSelectionMode); } void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) { - toolbar->removeTool (mSubMode); - delete mSubMode; - mSubMode = 0; + if (mSelectionMode) + { + toolbar->removeTool (mSelectionMode); + delete mSelectionMode; + mSelectionMode = 0; + } + + if (mSubMode) + { + toolbar->removeTool (mSubMode); + delete mSubMode; + mSubMode = 0; + } EditMode::deactivate (toolbar); } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 1eec62874d..78836878a4 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -10,10 +10,13 @@ namespace CSVWidget namespace CSVRender { + class InstanceSelectionMode; + class InstanceMode : public EditMode { Q_OBJECT CSVWidget::SceneToolMode *mSubMode; + InstanceSelectionMode *mSelectionMode; public: diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp new file mode 100644 index 0000000000..9fd8259991 --- /dev/null +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -0,0 +1,27 @@ + +#include "instanceselectionmode.hpp" + +CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar *parent) +: CSVWidget::SceneToolMode (parent, "Selection Mode") +{ + addButton (":placeholder", "cube-centre", + "Centred cube" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection cube outwards
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton (":placeholder", "cube-corner", + "Cube corner to corner" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite corner
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton (":placeholder", "sphere", + "Centred sphere" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection sphere outwards
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); +} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp new file mode 100644 index 0000000000..e823de2135 --- /dev/null +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -0,0 +1,19 @@ +#ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H +#define CSV_RENDER_INSTANCE_SELECTION_MODE_H + +#include "../widget/scenetoolmode.hpp" + +namespace CSVRender +{ + class InstanceSelectionMode : public CSVWidget::SceneToolMode + { + Q_OBJECT + + public: + + InstanceSelectionMode (CSVWidget::SceneToolbar *parent); + + }; +} + +#endif From eee972a1a4414a065e55153fe88413f7e6229cfc Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 19 Jan 2016 12:47:11 +0100 Subject: [PATCH 116/765] added scene tool mode context menu feature --- apps/opencs/view/widget/scenetoolmode.cpp | 17 +++++++++++++++++ apps/opencs/view/widget/scenetoolmode.hpp | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index a93bb05567..efb508f9db 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -3,10 +3,24 @@ #include #include #include +#include +#include #include "scenetoolbar.hpp" #include "modebutton.hpp" +void CSVWidget::SceneToolMode::contextMenuEvent (QContextMenuEvent *event) +{ + QMenu menu (this); + if (createContextMenu (&menu)) + menu.exec (event->globalPos()); +} + +bool CSVWidget::SceneToolMode::createContextMenu (QMenu *menu) +{ + return false; +} + void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) { QString toolTip = mToolTip; @@ -15,6 +29,9 @@ void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) toolTip += "

(left click to change mode)"; + if (createContextMenu (0)) + toolTip += "
(right click to access context menu)"; + setToolTip (toolTip); } diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 6828a22691..43cd4a7d75 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -6,6 +6,7 @@ #include class QHBoxLayout; +class QMenu; namespace CSVWidget { @@ -29,6 +30,16 @@ namespace CSVWidget void adjustToolTip (const ModeButton *activeMode); + virtual void contextMenuEvent (QContextMenuEvent *event); + + /// Add context menu items to \a menu. Default-implementation: return false + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu *menu); + public: SceneToolMode (SceneToolbar *parent, const QString& toolTip); From ecbcd6b1717718d1ccaa3e18d7ad930eb2244f61 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 19 Jan 2016 12:52:30 +0100 Subject: [PATCH 117/765] allow mode buttons to setup the context menu --- apps/opencs/view/widget/modebutton.cpp | 5 +++++ apps/opencs/view/widget/modebutton.hpp | 10 ++++++++++ apps/opencs/view/widget/scenetoolmode.cpp | 3 +++ apps/opencs/view/widget/scenetoolmode.hpp | 3 ++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 7c62f6bb16..88f0502479 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -7,3 +7,8 @@ CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QW void CSVWidget::ModeButton::activate (SceneToolbar *toolbar) {} void CSVWidget::ModeButton::deactivate (SceneToolbar *toolbar) {} + +bool CSVWidget::ModeButton::createContextMenu (QMenu *menu) +{ + return false; +} diff --git a/apps/opencs/view/widget/modebutton.hpp b/apps/opencs/view/widget/modebutton.hpp index ac14afc952..1615ff298a 100644 --- a/apps/opencs/view/widget/modebutton.hpp +++ b/apps/opencs/view/widget/modebutton.hpp @@ -3,6 +3,8 @@ #include "pushbutton.hpp" +class QMenu; + namespace CSVWidget { class SceneToolbar; @@ -22,6 +24,14 @@ namespace CSVWidget /// Default-Implementation: do nothing virtual void deactivate (SceneToolbar *toolbar); + + /// Add context menu items to \a menu. Default-implementation: return false + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu *menu); }; } diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index efb508f9db..125f4ac79a 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -18,6 +18,9 @@ void CSVWidget::SceneToolMode::contextMenuEvent (QContextMenuEvent *event) bool CSVWidget::SceneToolMode::createContextMenu (QMenu *menu) { + if (mCurrent) + return mCurrent->createContextMenu (menu); + return false; } diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 43cd4a7d75..3aa8e6799a 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -32,7 +32,8 @@ namespace CSVWidget virtual void contextMenuEvent (QContextMenuEvent *event); - /// Add context menu items to \a menu. Default-implementation: return false + /// Add context menu items to \a menu. Default-implementation: Pass on request to + /// current mode button or return false, if there is no current mode button. /// /// \attention menu can be a 0-pointer /// From 45e6974266f6df4c9185bf2b225309d19d969078 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 19 Jan 2016 14:25:20 +0100 Subject: [PATCH 118/765] added select all and clear selection features --- apps/opencs/view/render/instancemode.cpp | 3 +- .../view/render/instanceselectionmode.cpp | 37 ++++++++++++++++++- .../view/render/instanceselectionmode.hpp | 23 +++++++++++- .../view/render/pagedworldspacewidget.cpp | 9 +++++ .../view/render/pagedworldspacewidget.hpp | 3 ++ .../view/render/unpagedworldspacewidget.cpp | 6 +++ .../view/render/unpagedworldspacewidget.hpp | 3 ++ apps/opencs/view/render/worldspacewidget.hpp | 3 ++ 8 files changed, 82 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index ff49e0286e..4b6b2e41f8 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -9,7 +9,6 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" - #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" @@ -52,7 +51,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) } if (!mSelectionMode) - mSelectionMode = new InstanceSelectionMode (toolbar); + mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); EditMode::activate (toolbar); diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 9fd8259991..794a150d20 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -1,8 +1,25 @@ #include "instanceselectionmode.hpp" -CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar *parent) -: CSVWidget::SceneToolMode (parent, "Selection Mode") +#include +#include + +#include "worldspacewidget.hpp" + +bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) +{ + if (menu) + { + menu->addAction (mSelectAll); + menu->addAction (mDeselectAll); + } + + return true; +} + +CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar *parent, + WorldspaceWidget& worldspaceWidget) +: CSVWidget::SceneToolMode (parent, "Selection Mode"), mWorldspaceWidget (worldspaceWidget) { addButton (":placeholder", "cube-centre", "Centred cube" @@ -24,4 +41,20 @@ CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar "

  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " "" "Not implemented yet"); + + mSelectAll = new QAction ("Select all Instances", this); + mDeselectAll = new QAction ("Clear selection", this); + + connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); + connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); +} + +void CSVRender::InstanceSelectionMode::selectAll() +{ + mWorldspaceWidget.selectAll (Mask_Reference); +} + +void CSVRender::InstanceSelectionMode::clearSelection() +{ + mWorldspaceWidget.clearSelection (Mask_Reference); } diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index e823de2135..6b3a4e37da 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -3,16 +3,37 @@ #include "../widget/scenetoolmode.hpp" +class QAction; + namespace CSVRender { + class WorldspaceWidget; + class InstanceSelectionMode : public CSVWidget::SceneToolMode { Q_OBJECT + WorldspaceWidget& mWorldspaceWidget; + QAction *mSelectAll; + QAction *mDeselectAll; + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu *menu); + public: - InstanceSelectionMode (CSVWidget::SceneToolbar *parent); + InstanceSelectionMode (CSVWidget::SceneToolbar *parent, WorldspaceWidget& worldspaceWidget); + private slots: + + void selectAll(); + + void clearSelection(); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 1880beab8b..ccb3efb1d6 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -509,6 +509,15 @@ void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_All); + + flagAsModified(); +} + std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { const int cellSize = 8192; diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 647341d1f4..3f9e605af9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -98,6 +98,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; protected: diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index dad37c946b..48180f8665 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -108,6 +108,12 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) +{ + mCell->setSelection (elementMask, Cell::Selection_All); + flagAsModified(); +} + std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 70a20c216d..8971f22a84 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -46,6 +46,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; private: diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 7a77edad42..13e66b7f03 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -127,6 +127,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll (int elementMask) = 0; + /// Return the next intersection point with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. /// If there is no such point, instead a point "in front" of \a localPos will be From f0971ee8ad2f1e4d0a67a9aa6d5c6916c25ec91b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Jan 2016 04:07:07 +0100 Subject: [PATCH 119/765] Implement Fixme script instruction --- apps/openmw/mwscript/docs/vmformat.txt | 4 +++- apps/openmw/mwscript/transformationextensions.cpp | 14 ++++++++++++++ apps/openmw/mwworld/worldimp.cpp | 3 ++- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 ++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 42c204ecb0..93219c649b 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -447,5 +447,7 @@ op 0x20002fe: RemoveFromLevItem op 0x20002ff: SetFactionReaction op 0x2000300: EnableLevelupMenu op 0x2000301: ToggleScripts +op 0x2000302: Fixme +op 0x2000303: Fixme, explicit -opcodes 0x2000302-0x3ffffff unused +opcodes 0x2000304-0x3ffffff unused diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 60847e745b..64c126de12 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -734,6 +734,18 @@ namespace MWScript } }; + template + class OpFixme : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->fixPosition(ptr); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); @@ -774,6 +786,8 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); + interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); + interpreter.installSegment5(Compiler::Transformation::opcodeFixmeExplicit, new OpFixme); } } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c566a32b53..014c6b2002 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1311,7 +1311,8 @@ namespace MWWorld actor.getRefData().setPosition(pos); osg::Vec3f traced = mPhysics->traceDown(actor, dist*1.1f); - moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); + if (traced != pos.asVec3()) + moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 8c76cdbb80..6916945f93 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -541,6 +541,7 @@ namespace Compiler extensions.registerInstruction("moveworld","cf",opcodeMoveWorld,opcodeMoveWorldExplicit); extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); extensions.registerInstruction("resetactors","",opcodeResetActors); + extensions.registerInstruction("fixme","",opcodeFixme, opcodeFixmeExplicit); extensions.registerInstruction("ra","",opcodeResetActors); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index e7d51d934d..feed5513e3 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -498,6 +498,8 @@ namespace Compiler const int opcodeMoveWorld = 0x2000208; const int opcodeMoveWorldExplicit = 0x2000209; const int opcodeResetActors = 0x20002f4; + const int opcodeFixme = 0x2000302; + const int opcodeFixmeExplicit = 0x2000303; } namespace User From 339fbe23a7c0f1d89e683f6fa41728a7b5c3d428 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Jan 2016 14:03:51 +0100 Subject: [PATCH 120/765] remove precise cruft --- CI/before_install.linux.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 6e288aa149..1c02bc8d99 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,22 +1,17 @@ #!/bin/sh if [ "${ANALYZE}" ]; then - if [ $(lsb_release -sc) = "precise" ]; then - echo "yes" | sudo apt-add-repository ppa:ubuntu-toolchain-r/test - fi echo "yes" | sudo add-apt-repository "deb http://llvm.org/apt/`lsb_release -sc`/ llvm-toolchain-`lsb_release -sc`-3.6 main" wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - fi echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" echo "yes" | sudo apt-add-repository ppa:openmw/openmw -echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock -sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev -sudo apt-get install -qq ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev +sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev +sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev -sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build From b8d19eb9cd09324b456c23a3f968a51d03432596 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Jan 2016 14:30:33 +0100 Subject: [PATCH 121/765] make use of 2 dedicated cores while assuming both are blocked on I/O --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 10b47ee7dd..3a76944fbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ before_script: - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi script: - cd ./build - - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi From 10609ca5d1d9e6abaa76978a06b830eaaa217530 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 21 Jan 2016 09:22:42 +0100 Subject: [PATCH 122/765] how much memory in our vm? --- CI/before_script.linux.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 71ddd20407..17667ad28a 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -1,5 +1,6 @@ #!/bin/sh +free -m mkdir build cd build export CODE_COVERAGE=1 From 89512af808d78cf21e2e6553d155d1d341c4e54d Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Jan 2016 14:33:26 +0100 Subject: [PATCH 123/765] Increase number of jobs for coverity scan build to 2, enable OpenMW-CS build As of the recent travis migration we should have enough memory to not run out... hopefully. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a76944fbe..e314d8e7b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ addons: name: "OpenMW/openmw" description: "" notification_email: scrawl@baseoftrash.de - build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE" - build_command: "make" + build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE" + build_command: "make -j2" branch_pattern: coverity_scan matrix: include: From b0431833a1c8c077d2858faa62b96d9564efc3be Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Jan 2016 16:08:04 +0100 Subject: [PATCH 124/765] Fix some defects reported by Coverity CI --- apps/opencs/model/doc/messages.cpp | 2 +- apps/opencs/model/doc/messages.hpp | 3 --- apps/opencs/model/doc/operationholder.cpp | 4 +++- apps/opencs/model/tools/reportmodel.cpp | 2 +- apps/opencs/model/tools/reportmodel.hpp | 2 +- apps/opencs/model/world/commands.cpp | 2 +- apps/opencs/view/render/worldspacewidget.cpp | 5 +++-- apps/opencs/view/world/table.cpp | 2 +- apps/opencs/view/world/tablebottombox.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +++- 11 files changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index 86e96a88d6..76bbb6f227 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,6 +1,6 @@ #include "messages.hpp" -CSMDoc::Message::Message() {} +CSMDoc::Message::Message() : mSeverity(Severity_Default){} CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity) diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 429feae4ec..4041e1a678 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -39,9 +39,6 @@ namespace CSMDoc { public: - // \deprecated Use CSMDoc::Message directly instead. - typedef CSMDoc::Message Message; - typedef std::vector Collection; typedef Collection::const_iterator Iterator; diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 5fcf24fe4f..ccbed6c8ba 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -2,7 +2,9 @@ #include "operation.hpp" -CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) +CSMDoc::OperationHolder::OperationHolder (Operation *operation) + : mOperation(NULL) + , mRunning (false) { if (operation) setOperation (operation); diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 77a14de841..49de1d6512 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -182,7 +182,7 @@ int CSMTools::ReportModel::countErrors() const { int count = 0; - for (std::vector::const_iterator iter (mRows.begin()); + for (std::vector::const_iterator iter (mRows.begin()); iter!=mRows.end(); ++iter) if (iter->mSeverity==CSMDoc::Message::Severity_Error || iter->mSeverity==CSMDoc::Message::Severity_SeriousError) diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 5704970f55..61b4e63074 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -16,7 +16,7 @@ namespace CSMTools { Q_OBJECT - std::vector mRows; + std::vector mRows; // Fixed columns enum Columns diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index d510cd1038..097e83f7ca 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -14,7 +14,7 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) -: QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false) + : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) { if (QAbstractProxyModel *proxy = dynamic_cast (&model)) { diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 184477d6b2..af90af50cc 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -33,8 +33,9 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), - mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), - mToolTipPos (-1, -1) + mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), mDragX(0), mDragY(0), mDragFactor(0), + mDragWheelFactor(0), mDragShiftFactor(0), + mToolTipPos (-1, -1), mShowToolTips(false), mToolTipDelay(0) { setAcceptDrops(true); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 95dfa10340..26746e8c91 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -231,7 +231,7 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : DragRecordTable(document), mCreateAction (0), - mCloneAction(0),mRecordStatusDisplay (0) + mCloneAction(0), mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false) { mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index eed522227e..5a25bbc53e 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -76,7 +76,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b8dc90cf7a..84da270ebb 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1556,7 +1556,7 @@ namespace MWMechanics (target == getPlayer() && MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { - const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().search("iWerewolfFightMod"); + const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().find("iWerewolfFightMod"); fight += iWerewolfFightMod->getInt(); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 014c6b2002..afb5be9d35 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -433,7 +433,7 @@ namespace MWWorld // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.f); gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(1.f); - + gmst["iWerewolfFightMod"] = ESM::Variant(1); std::map globals; // vanilla Morrowind does not define dayspassed. @@ -1745,6 +1745,8 @@ namespace MWWorld { cellid.mWorldspace = ref.mRef.getDestCell(); cellid.mPaged = false; + cellid.mIndex.mX = 0; + cellid.mIndex.mY = 0; } else { From ffcb6ec3819a3b3c0b66829bc2341c090254f737 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Jan 2016 23:38:23 +0100 Subject: [PATCH 125/765] Change the default near clip distance to 1 (Fixes #3155) --- files/settings-default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6a2495e267..e4a8387b4b 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -14,7 +14,7 @@ [Camera] # Near clipping plane (>0.0, e.g. 0.01 to 18.0). -near clip = 5.0 +near clip = 1 # Cull objects smaller than one pixel. small feature culling = true From 07a4c0bf739ced2b2975e9c6507584d6fc4afc8e Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 23 Jan 2016 01:20:31 +0100 Subject: [PATCH 126/765] version bump to 0.38 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f353cd76ef..9cfb0440db 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. -* Version: 0.37.0 +* Version: 0.38.0 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net From b93b97575eafe176d405d047040fa13b4ddd80e4 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 24 Jan 2016 11:54:53 -0500 Subject: [PATCH 127/765] Improve start script creation (Fixes #3024) Improved start script creation by updating input to be a drop target and adding auto-completion. --- apps/opencs/view/world/startscriptcreator.cpp | 132 ++++++++++++++++-- apps/opencs/view/world/startscriptcreator.hpp | 70 ++++++++-- apps/opencs/view/world/subviews.cpp | 7 +- 3 files changed, 186 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 69b1b3ff12..57ac34b8f0 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -1,20 +1,130 @@ #include "startscriptcreator.hpp" -CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): - GenericCreator (data, undoStack, id, true) -{} +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/columns.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/idtable.hpp" + +#include "../widget/droplineedit.hpp" + +std::string CSVWorld::StartScriptCreator::getId() const +{ + return mScript->text().toUtf8().constData(); +} + +CSMWorld::IdTable& CSVWorld::StartScriptCreator::getStartScriptsTable() const +{ + return dynamic_cast ( + *getData().getTableModel(getCollectionId()) + ); +} + +void CSVWorld::StartScriptCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const +{ + CSMWorld::IdTable& table = getStartScriptsTable(); + int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + + // Set script ID to be added to start scripts table. + command.addValue(column, mScript->text()); +} + +CSVWorld::StartScriptCreator::StartScriptCreator( + CSMWorld::Data &data, + QUndoStack &undoStack, + const CSMWorld::UniversalId &id, + CSMWorld::IdCompletionManager& completionManager +) : GenericCreator(data, undoStack, id, true) +{ + setManualEditing(false); + + // Add script ID input label. + QLabel *label = new QLabel("Script ID", this); + insertBeforeButtons(label, false); + + // Add script ID input with auto-completion. + CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Script; + mScript = new CSVWidget::DropLineEdit(displayType, this); + mScript->setCompleter(completionManager.getCompleter(displayType).get()); + insertBeforeButtons(mScript, true); + + connect(mScript, SIGNAL (textChanged(const QString&)), this, SLOT (scriptChanged())); +} + +void CSVWorld::StartScriptCreator::cloneMode( + const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + + // Look up cloned record in start scripts table and set script ID text. + CSMWorld::IdTable& table = getStartScriptsTable(); + int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + mScript->setText(table.data(table.getModelIndex(originId, column)).toString()); +} std::string CSVWorld::StartScriptCreator::getErrors() const { - std::string errors; + std::string scriptId = getId(); - errors = getIdValidatorResult(); - if (errors.length() > 0) - return errors; - else if (getData().getScripts().searchId(getId()) == -1) - errors = "Script ID not found"; - else if (getData().getStartScripts().searchId(getId()) > -1 ) - errors = "Script with this ID already registered as Start Script"; + // Check user input for any errors. + std::string errors; + if (scriptId.empty()) + { + if (!errors.empty()) + { + errors += "
    "; + } + errors += "No Script ID entered"; + } + else if (getData().getScripts().searchId(scriptId) == -1) + { + if (!errors.empty()) + { + errors += "
    "; + } + errors += "Script ID not found"; + } + else if (getData().getStartScripts().searchId(scriptId) > -1) + { + if (!errors.empty()) + { + errors += "
    "; + } + errors += "Script with this ID already registered as Start Script"; + } return errors; } + +void CSVWorld::StartScriptCreator::focus() +{ + mScript->setFocus(); +} + +void CSVWorld::StartScriptCreator::reset() +{ + CSVWorld::GenericCreator::reset(); + mScript->setText(""); +} + +void CSVWorld::StartScriptCreator::scriptChanged() +{ + update(); +} + +CSVWorld::Creator *CSVWorld::StartScriptCreatorFactory::makeCreator( + CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const +{ + return new StartScriptCreator( + document.getData(), + document.getUndoStack(), + id, + document.getIdCompletionManager() + ); +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index 07fe8ff3d3..745dce819e 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -3,23 +3,77 @@ #include "genericcreator.hpp" -namespace CSVWorld { +namespace CSMWorld +{ + class IdCompletionManager; + class IdTable; +} +namespace CSVWidget +{ + class DropLineEdit; +} + +namespace CSVWorld +{ + /// \brief Record creator for start scripts. class StartScriptCreator : public GenericCreator { Q_OBJECT - public: - StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + CSVWidget::DropLineEdit *mScript; + private: + + /// \return script ID entered by user. + virtual std::string getId() const; + + /// \return reference to table containing start scripts. + CSMWorld::IdTable& getStartScriptsTable() const; + + /// \brief Add user input to command for creating start script. + /// \param command Creation command to configure. + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + + public: + + StartScriptCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); + + /// \brief Set script ID input widget to ID of record to be cloned. + /// \param originId Script ID to be cloned. + /// \param type Type of record to be cloned. + virtual void cloneMode( + const std::string& originId, + const CSMWorld::UniversalId::Type type); + + /// \return Formatted error descriptions for current user input. virtual std::string getErrors() const; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. + + /// \brief Set focus to script ID input widget. + virtual void focus(); + + /// \brief Clear script ID input widget. + virtual void reset(); + + private slots: + + /// \brief Check user input for any errors. + void scriptChanged(); }; + /// \brief Creator factory for start script record creator. + class StartScriptCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator( + CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const; + }; } - - #endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 88375caafc..650f344ed9 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -52,7 +52,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_StartScripts, - new CSVDoc::SubViewFactoryWithCreator >); + new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -136,8 +136,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CreatorFactory > (false)); manager.add (CSMWorld::UniversalId::Type_StartScript, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); @@ -177,7 +176,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_MetaData, new CSVDoc::SubViewFactory); - + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } From fbca094dda2a23860a74561dd994de8b0dab33aa Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 24 Jan 2016 20:15:27 -0500 Subject: [PATCH 128/765] Fix input validation in StartScriptCreator --- apps/opencs/view/world/startscriptcreator.cpp | 18 +++--------------- apps/opencs/view/world/startscriptcreator.hpp | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 57ac34b8f0..7495da035e 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -75,27 +75,15 @@ std::string CSVWorld::StartScriptCreator::getErrors() const std::string errors; if (scriptId.empty()) { - if (!errors.empty()) - { - errors += "
    "; - } - errors += "No Script ID entered"; + errors = "No Script ID entered"; } else if (getData().getScripts().searchId(scriptId) == -1) { - if (!errors.empty()) - { - errors += "
    "; - } - errors += "Script ID not found"; + errors = "Script ID not found"; } else if (getData().getStartScripts().searchId(scriptId) > -1) { - if (!errors.empty()) - { - errors += "
    "; - } - errors += "Script with this ID already registered as Start Script"; + errors = "Script with this ID already registered as Start Script"; } return errors; diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index 745dce819e..473e2fd5f6 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -50,7 +50,7 @@ namespace CSVWorld const std::string& originId, const CSMWorld::UniversalId::Type type); - /// \return Formatted error descriptions for current user input. + /// \return Error description for current user input. virtual std::string getErrors() const; /// \brief Set focus to script ID input widget. From 0659687bfbdf859414313aed33d9da7cef80efec Mon Sep 17 00:00:00 2001 From: Tobias Kortkamp Date: Mon, 25 Jan 2016 14:13:16 +0100 Subject: [PATCH 129/765] Some fixes for building on FreeBSD --- apps/essimporter/converter.hpp | 2 +- apps/essimporter/convertinventory.cpp | 1 + apps/openmw/crashcatcher.cpp | 2 +- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwmechanics/spells.hpp | 2 +- apps/openmw/mwworld/projectilemanager.hpp | 1 + apps/openmw/mwworld/refdata.hpp | 1 + apps/openmw/mwworld/worldimp.cpp | 4 ++++ components/esm/spellstate.cpp | 6 +++--- components/esm/spellstate.hpp | 2 +- components/resource/bulletshape.cpp | 1 + components/sceneutil/lightcontroller.cpp | 1 + components/sceneutil/lightutil.cpp | 1 + 13 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index f364e166c8..81b2bec14a 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -121,7 +121,7 @@ public: { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayerBase = npc; - std::map empty; + std::map empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index f476fe1ee2..0799c8d113 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -1,6 +1,7 @@ #include "convertinventory.hpp" #include +#include namespace ESSImport { diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 0b4ff6304f..cafd0e08a1 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -24,7 +24,7 @@ #ifndef PR_SET_PTRACER #define PR_SET_PTRACER 0x59616d61 #endif -#elif defined (__APPLE__) +#elif defined (__APPLE__) || defined (__FreeBSD__) #include #endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a88b6b2633..2f87d04463 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -44,7 +44,7 @@ namespace MWMechanics { if (mSpells.find (spell)==mSpells.end()) { - std::map random; + std::map random; // Determine the random magnitudes (unless this is a castable spell, in which case // they will be determined when the spell is cast) diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 1b1993d5ef..a46988c4c5 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -33,7 +33,7 @@ namespace MWMechanics typedef const ESM::Spell* SpellKey; - typedef std::map > TContainer; // ID, + typedef std::map > TContainer; // ID, typedef TContainer::const_iterator TIterator; struct CorprusStats diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 74d4c1dc53..02ac6cb55d 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -3,6 +3,7 @@ #include +#include #include #include diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 28d2dad4c9..d87ffdb70e 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -5,6 +5,7 @@ #include "../mwscript/locals.hpp" +#include #include namespace SceneUtil diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index afb5be9d35..37743bceb0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -108,7 +108,11 @@ namespace MWWorld } private: +#ifdef HAVE_UNORDERED_MAP + typedef std::unordered_map LoadersContainer; +#else typedef std::tr1::unordered_map LoadersContainer; +#endif LoadersContainer mLoaders; }; diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index 3ed3329b46..8845842e9f 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -12,7 +12,7 @@ namespace ESM { std::string id = esm.getHString(); - std::map random; + std::map random; while (esm.isNextSub("INDX")) { int index; @@ -73,8 +73,8 @@ namespace ESM { esm.writeHNString("SPEL", it->first); - const std::map& random = it->second; - for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) + const std::map& random = it->second; + for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) { esm.writeHNT("INDX", rIt->first); esm.writeHNT("RAND", rIt->second); diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index 028e6a3878..503c3aea9f 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -28,7 +28,7 @@ namespace ESM float mMagnitude; }; - typedef std::map > TContainer; + typedef std::map > TContainer; TContainer mSpells; std::map > mPermanentSpellEffects; diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 968dbe6c67..0cbc63a229 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -1,6 +1,7 @@ #include "bulletshape.hpp" #include +#include #include #include diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index e3ea93843c..47575e2e6d 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -2,6 +2,7 @@ #include +#include #include #include diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 6499c54b1d..979d41181b 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -1,5 +1,6 @@ #include "lightutil.hpp" +#include #include #include #include From c1f0aa7260059007262aa1f4d21555fb1827c10b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 25 Jan 2016 14:55:02 +0100 Subject: [PATCH 130/765] added delete selection item to selection mode button menu --- apps/opencs/view/render/cell.cpp | 13 ++++++++++ apps/opencs/view/render/cell.hpp | 4 +++ .../view/render/instanceselectionmode.cpp | 26 +++++++++++++++++++ .../view/render/instanceselectionmode.hpp | 3 +++ apps/opencs/view/render/object.cpp | 5 ++++ apps/opencs/view/render/object.hpp | 2 ++ .../view/render/pagedworldspacewidget.cpp | 17 ++++++++++++ .../view/render/pagedworldspacewidget.hpp | 3 +++ .../view/render/unpagedworldspacewidget.cpp | 6 +++++ .../view/render/unpagedworldspacewidget.hpp | 3 +++ apps/opencs/view/render/worldspacewidget.hpp | 3 +++ 11 files changed, 85 insertions(+) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 40981164de..4372018fb8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -276,3 +276,16 @@ bool CSVRender::Cell::isDeleted() const { return mDeleted; } + +std::vector > CSVRender::Cell::getSelection (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->getSelected()) + result.push_back (iter->second->getTag()); + + return result; +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 59f4cafee4..3e51bb3342 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -31,6 +31,8 @@ namespace CSMWorld namespace CSVRender { + class TagBase; + class Cell { CSMWorld::Data& mData; @@ -99,6 +101,8 @@ namespace CSVRender CSMWorld::CellCoordinates getCoordinates() const; bool isDeleted() const; + + std::vector > getSelection (unsigned int elementMask) const; }; } diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 794a150d20..754123d2d9 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -4,7 +4,11 @@ #include #include +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" + #include "worldspacewidget.hpp" +#include "object.hpp" bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) { @@ -12,6 +16,7 @@ bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) { menu->addAction (mSelectAll); menu->addAction (mDeselectAll); + menu->addAction (mDeleteSelection); } return true; @@ -44,9 +49,11 @@ CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar mSelectAll = new QAction ("Select all Instances", this); mDeselectAll = new QAction ("Clear selection", this); + mDeleteSelection = new QAction ("Delete selection", this); connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); + connect (mDeleteSelection, SIGNAL (triggered ()), this, SLOT (deleteSelection())); } void CSVRender::InstanceSelectionMode::selectAll() @@ -58,3 +65,22 @@ void CSVRender::InstanceSelectionMode::clearSelection() { mWorldspaceWidget.clearSelection (Mask_Reference); } + +void CSVRender::InstanceSelectionMode::deleteSelection() +{ + std::vector > selection = + mWorldspaceWidget.getSelection (Mask_Reference); + + CSMWorld::IdTable& referencesTable = + dynamic_cast (*mWorldspaceWidget.getDocument().getData(). + getTableModel (CSMWorld::UniversalId::Type_References)); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + CSMWorld::DeleteCommand *command = new CSMWorld::DeleteCommand (referencesTable, + static_cast (iter->get())->mObject->getReferenceId()); + + mWorldspaceWidget.getDocument().getUndoStack().push (command); + } +} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index 6b3a4e37da..07b774543d 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -16,6 +16,7 @@ namespace CSVRender WorldspaceWidget& mWorldspaceWidget; QAction *mSelectAll; QAction *mDeselectAll; + QAction *mDeleteSelection; /// Add context menu items to \a menu. /// @@ -34,6 +35,8 @@ namespace CSVRender void selectAll(); void clearSelection(); + + void deleteSelection(); }; } diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 1821da059e..dcf217a364 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -284,3 +284,8 @@ std::string CSVRender::Object::getReferenceableId() const { return mReferenceableId; } + +osg::ref_ptr CSVRender::Object::getTag() const +{ + return static_cast (mBaseNode->getUserData()); +} diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e7638e7a99..4a89fe201f 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -114,6 +114,8 @@ namespace CSVRender std::string getReferenceId() const; std::string getReferenceableId() const; + + osg::ref_ptr getTag() const; }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ccb3efb1d6..ef5c4e868a 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -529,6 +529,23 @@ std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point return cellCoordinates.getId (mWorldspace); } +std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( + unsigned int elementMask) const +{ + std::vector > result; + + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + { + std::vector > cellResult = + iter->second->getSelection (elementMask); + + result.insert (result.end(), cellResult.begin(), cellResult.end()); + } + + return result; +} + CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 3f9e605af9..bd4233a644 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -103,6 +103,9 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual std::vector > getSelection (unsigned int elementMask) + const; + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 48180f8665..a026a24732 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -119,6 +119,12 @@ std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& poi return mCellId; } +std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( + unsigned int elementMask) const +{ + return mCell->getSelection (elementMask); +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 8971f22a84..3aea8dee4a 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -51,6 +51,9 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual std::vector > getSelection (unsigned int elementMask) + const; + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 13e66b7f03..cd031fcba4 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -143,6 +143,9 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const = 0; + virtual std::vector > getSelection (unsigned int elementMask) + const = 0; + protected: /// Visual elements in a scene From 790367b980ef6980b62abb30b61890100c7aca60 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 25 Jan 2016 16:12:20 +0100 Subject: [PATCH 131/765] fixed object removal via setting state to delete --- apps/opencs/view/render/cell.cpp | 40 +++++++++++++++++++++----------- apps/opencs/view/render/cell.hpp | 4 ++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 4372018fb8..758498de16 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -22,11 +22,18 @@ bool CSVRender::Cell::removeObject (const std::string& id) if (iter==mObjects.end()) return false; - delete iter->second; - mObjects.erase (iter); + removeObject (iter); return true; } +std::map::iterator CSVRender::Cell::removeObject ( + std::map::iterator iter) +{ + delete iter->second; + mObjects.erase (iter++); + return iter; +} + bool CSVRender::Cell::addObjects (int start, int end) { bool modified = false; @@ -161,8 +168,8 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, // perform update and remove where needed bool modified = false; - for (std::map::iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + std::map::iterator iter = mObjects.begin(); + while (iter!=mObjects.end()) { if (iter->second->referenceDataChanged (topLeft, bottomRight)) modified = true; @@ -171,23 +178,30 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, if (iter2!=ids.end()) { - if (iter2->second) - { - removeObject (iter->first); - modified = true; - } - + bool deleted = iter2->second; ids.erase (iter2); + + if (deleted) + { + iter = removeObject (iter); + modified = true; + continue; + } } + + ++iter; } // add new objects for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) { - mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false))); + if (!iter->second) + { + mObjects.insert (std::make_pair ( + iter->first, new Object (mData, mCellNode, iter->first, false))); - modified = true; + modified = true; + } } return modified; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 3e51bb3342..9f062b4392 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -49,6 +49,10 @@ namespace CSVRender /// \return Was the object deleted? bool removeObject (const std::string& id); + // Remove object and return iterator to next object. + std::map::iterator removeObject ( + std::map::iterator iter); + /// Add objects from reference table that are within this cell. /// /// \return Have any objects been added? From 39e1b06101ab6947fec43470fa0225060a9cc80b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 25 Jan 2016 16:18:55 +0100 Subject: [PATCH 132/765] fixed deletion of objects that are selected (was leaving a node behind) --- apps/opencs/view/render/object.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index dcf217a364..b980b658a4 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -187,6 +187,7 @@ CSVRender::Object::~Object() clear(); mParentNode->removeChild(mBaseNode); + mParentNode->removeChild(mOutline); } void CSVRender::Object::setSelected(bool selected) From b393d34ebb84f056a20aaa495c5b9509d2334837 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 25 Jan 2016 16:39:20 +0100 Subject: [PATCH 133/765] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index bfbd14a5b8..83a625406e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -113,6 +113,7 @@ Programmers Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) Sylvain Thesnieres (Garvek) + t6 terrorfisch Thomas Luppi (Digmaster) Tom Mason (wheybags) From 48f53e23bfaba6ea8aa32536aeb5386e5e03a4c3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Jan 2016 16:59:31 +0100 Subject: [PATCH 134/765] Allow alternate mesh formats for marker_error --- components/resource/scenemanager.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 1d1c5a3656..e142826490 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -201,10 +201,22 @@ namespace Resource } catch (std::exception& e) { - std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error.nif instead" << std::endl; - Files::IStreamPtr file = mVFS->get("meshes/marker_error.nif"); - normalized = "meshes/marker_error.nif"; - loaded = load(file, normalized, mTextureManager, mNifFileManager); + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + + for (unsigned int i=0; iexists(normalized)) + { + std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead" << std::endl; + Files::IStreamPtr file = mVFS->get(normalized); + loaded = load(file, normalized, mTextureManager, mNifFileManager); + break; + } + } + + if (!loaded) + throw; } osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); From fc6fe9acfb3f70d65c97fc45d9d3b0a8b1b16236 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Jan 2016 18:52:20 +0100 Subject: [PATCH 135/765] Do not crash ModVertexAlphaVisitor when there are no vertex colors --- apps/openmw/mwrender/sky.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index efe797336d..692e3655a0 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -374,8 +374,13 @@ public: } else if (mMeshType == 2) { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + if (geom->getColorArray()) + { + osg::Vec4Array* origColors = static_cast(geom->getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + else + alpha = 1.f; } (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); From aec8c3846103da0d65ca772d859c501f2d6d9c57 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Jan 2016 21:03:33 +0100 Subject: [PATCH 136/765] Move observer_ptr include where it belongs --- apps/openmw/mwrender/sky.cpp | 1 + apps/openmw/mwworld/projectilemanager.hpp | 1 - components/sceneutil/lightcontroller.cpp | 1 - components/sceneutil/lightmanager.hpp | 1 + components/sceneutil/lightutil.cpp | 1 - 5 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 692e3655a0..513fc74a34 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 02ac6cb55d..74d4c1dc53 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 47575e2e6d..e3ea93843c 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -2,7 +2,6 @@ #include -#include #include #include diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 07b3b4a62e..0d8610eadb 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace SceneUtil { diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 979d41181b..6499c54b1d 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -1,6 +1,5 @@ #include "lightutil.hpp" -#include #include #include #include From d9290b0ee0fa25c6655a4b054f979ea6ed186f07 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Jan 2016 21:13:38 +0100 Subject: [PATCH 137/765] Array fix --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e142826490..43ece41f35 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -201,7 +201,7 @@ namespace Resource } catch (std::exception& e) { - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2" }; for (unsigned int i=0; i Date: Tue, 26 Jan 2016 10:51:47 +0100 Subject: [PATCH 138/765] minor improvements to wording of instance selection menu items --- apps/opencs/view/render/instanceselectionmode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 754123d2d9..2fd91c8e2d 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -47,9 +47,9 @@ CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar "" "Not implemented yet"); - mSelectAll = new QAction ("Select all Instances", this); + mSelectAll = new QAction ("Select all instances", this); mDeselectAll = new QAction ("Clear selection", this); - mDeleteSelection = new QAction ("Delete selection", this); + mDeleteSelection = new QAction ("Delete selected instances", this); connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); From 1d0ef97bf6bc566fa817e51179467a854c925365 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 26 Jan 2016 11:31:37 +0100 Subject: [PATCH 139/765] added select all of same ID item to selection mode button menu --- apps/opencs/view/render/cell.cpp | 22 +++++++++++++++++++ apps/opencs/view/render/cell.hpp | 4 ++++ .../view/render/instanceselectionmode.cpp | 9 +++++++- .../view/render/instanceselectionmode.hpp | 3 +++ .../view/render/pagedworldspacewidget.cpp | 9 ++++++++ .../view/render/pagedworldspacewidget.hpp | 6 +++++ .../view/render/unpagedworldspacewidget.cpp | 6 +++++ .../view/render/unpagedworldspacewidget.hpp | 6 +++++ apps/opencs/view/render/worldspacewidget.hpp | 6 +++++ 9 files changed, 70 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 758498de16..bd85c8a142 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -263,6 +263,28 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) } } +void CSVRender::Cell::selectAllWithSameParentId (int elementMask) +{ + std::set ids; + + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (iter->second->getSelected()) + ids.insert (iter->second->getReferenceableId()); + } + + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (!iter->second->getSelected() && + ids.find (iter->second->getReferenceableId())!=ids.end()) + { + iter->second->setSelected (true); + } + } +} + void CSVRender::Cell::setCellArrows (int mask) { for (int i=0; i<4; ++i) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 9f062b4392..85b9bf21b4 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -99,6 +99,10 @@ namespace CSVRender void setSelection (int elementMask, Selection mode); + // Select everything that references the same ID as at least one of the elements + // already selected + void selectAllWithSameParentId (int elementMask); + void setCellArrows (int mask); /// Returns 0, 0 in case of an unpaged cell. diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 2fd91c8e2d..5c3aaa8d16 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -16,6 +16,7 @@ bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) { menu->addAction (mSelectAll); menu->addAction (mDeselectAll); + menu->addAction (mSelectSame); menu->addAction (mDeleteSelection); } @@ -50,10 +51,11 @@ CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar mSelectAll = new QAction ("Select all instances", this); mDeselectAll = new QAction ("Clear selection", this); mDeleteSelection = new QAction ("Delete selected instances", this); - + mSelectSame = new QAction ("Extend selection to instances with same object ID", this); connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); connect (mDeleteSelection, SIGNAL (triggered ()), this, SLOT (deleteSelection())); + connect (mSelectSame, SIGNAL (triggered ()), this, SLOT (selectSame())); } void CSVRender::InstanceSelectionMode::selectAll() @@ -84,3 +86,8 @@ void CSVRender::InstanceSelectionMode::deleteSelection() mWorldspaceWidget.getDocument().getUndoStack().push (command); } } + +void CSVRender::InstanceSelectionMode::selectSame() +{ + mWorldspaceWidget.selectAllWithSameParentId (Mask_Reference); +} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index 07b774543d..cac2ca8c20 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -17,6 +17,7 @@ namespace CSVRender QAction *mSelectAll; QAction *mDeselectAll; QAction *mDeleteSelection; + QAction *mSelectSame; /// Add context menu items to \a menu. /// @@ -37,6 +38,8 @@ namespace CSVRender void clearSelection(); void deleteSelection(); + + void selectSame(); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ef5c4e868a..d71446d0b3 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -518,6 +518,15 @@ void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->selectAllWithSameParentId (elementMask); + + flagAsModified(); +} + std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { const int cellSize = 8192; diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index bd4233a644..419a2a46a2 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -101,6 +101,12 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; virtual std::vector > getSelection (unsigned int elementMask) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index a026a24732..8d65c36948 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -114,6 +114,12 @@ void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +{ + mCell->selectAllWithSameParentId (elementMask); + flagAsModified(); +} + std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 3aea8dee4a..a4c517948d 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -49,6 +49,12 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask); + virtual std::string getCellId (const osg::Vec3f& point) const; virtual std::vector > getSelection (unsigned int elementMask) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index cd031fcba4..ac6426b42e 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -130,6 +130,12 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask) = 0; + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId (int elementMask) = 0; + /// Return the next intersection point with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. /// If there is no such point, instead a point "in front" of \a localPos will be From 4caf44f06156e91920d4e931a617ea8c93693245 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Tue, 26 Jan 2016 07:48:55 -0500 Subject: [PATCH 140/765] Remove unused code in apps/opencs/view/world/table files --- apps/opencs/view/world/table.cpp | 7 ------- apps/opencs/view/world/table.hpp | 2 -- 2 files changed, 9 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 26746e8c91..45e50dba7b 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -12,7 +11,6 @@ #include "../../model/doc/document.hpp" -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/infotableproxymodel.hpp" #include "../../model/world/idtableproxymodel.hpp" @@ -20,13 +18,10 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" -#include "recordstatusdelegate.hpp" #include "tableeditidaction.hpp" #include "util.hpp" @@ -339,8 +334,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), this, SLOT (rowAdded (const std::string &))); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 53249f66fe..768ff185d5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -11,7 +11,6 @@ #include "../../model/world/universalid.hpp" #include "dragrecordtable.hpp" -class QUndoStack; class QAction; namespace CSMDoc @@ -21,7 +20,6 @@ namespace CSMDoc namespace CSMWorld { - class Data; class IdTableProxyModel; class IdTableBase; class CommandDispatcher; From d43315fe4e4869fc026587a1cd468a4bf485dfde Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Thu, 28 Jan 2016 06:28:31 -0500 Subject: [PATCH 141/765] Add script editor line wrapping (Feature #2926) Adds line wrapping for script editor and associated entry in settings window. No line wrapping remains the default. --- apps/opencs/model/prefs/state.cpp | 2 ++ apps/opencs/view/world/scriptedit.cpp | 51 +++++++++++++++++++-------- apps/opencs/view/world/scriptedit.hpp | 10 +++++- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 859fabd117..fd777b1331 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -133,6 +133,8 @@ void CSMPrefs::State::declare() declareBool ("show-linenum", "Show Line Numbers", true). setTooltip ("Show line numbers to the left of the script editor window." "The current row and column numbers of the text cursor are shown at the bottom."); + declareBool ("wrap-lines", "Wrap Lines", false). + setTooltip ("Wrap lines longer than width of script editor."); declareBool ("mono-font", "Use monospace font", true); EnumValue warningsNormal ("Normal", "Report warnings as warning"); declareEnum ("warnings", "Warning Mode", warningsNormal). diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 9f1abcf970..d0146445ac 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -38,20 +38,20 @@ bool CSVWorld::ScriptEdit::event (QEvent *event) return QPlainTextEdit::event (event); } -CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, - QWidget* parent) - : QPlainTextEdit (parent), - mChangeLocked (0), +CSVWorld::ScriptEdit::ScriptEdit( + const CSMDoc::Document& document, + ScriptHighlighter::Mode mode, + QWidget* parent +) : QPlainTextEdit(parent), + mChangeLocked(0), mShowLineNum(false), mLineNumberArea(0), mDefaultFont(font()), mMonoFont(QFont("Monospace")), - mDocument (document), + mDocument(document), mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) - { -// setAcceptRichText (false); - setLineWrapMode (QPlainTextEdit::NoWrap); + wrapLines(false); setTabStopWidth (4); setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead @@ -194,14 +194,37 @@ bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const return !(string.contains(mWhiteListQoutes)); } -void CSVWorld::ScriptEdit::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::ScriptEdit::wrapLines(bool wrap) { - if (mHighlighter->settingChanged (setting)) + if (wrap) + { + setLineWrapMode(QPlainTextEdit::WidgetWidth); + } + else + { + setLineWrapMode(QPlainTextEdit::NoWrap); + } +} + +void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting *setting) +{ + // Determine which setting was changed. + if (mHighlighter->settingChanged(setting)) + { updateHighlighting(); - else if (*setting=="Scripts/mono-font") - setFont (setting->isTrue() ? mMonoFont : mDefaultFont); - else if (*setting=="Scripts/show-linenum") - showLineNum (setting->isTrue()); + } + else if (*setting == "Scripts/mono-font") + { + setFont(setting->isTrue() ? mMonoFont : mDefaultFont); + } + else if (*setting == "Scripts/show-linenum") + { + showLineNum(setting->isTrue()); + } + else if (*setting == "Scripts/wrap-lines") + { + wrapLines(setting->isTrue()); + } } void CSVWorld::ScriptEdit::idListChanged() diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index 941a6295d8..1f03f050a2 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -22,6 +22,7 @@ namespace CSVWorld { class LineNumberArea; + /// \brief Editor for scripts. class ScriptEdit : public QPlainTextEdit { Q_OBJECT @@ -77,6 +78,7 @@ namespace CSVWorld virtual void resizeEvent(QResizeEvent *e); private: + QVector mAllowedTypes; const CSMDoc::Document& mDocument; const QRegExp mWhiteListQoutes; @@ -89,9 +91,15 @@ namespace CSVWorld bool stringNeedsQuote(const std::string& id) const; + /// \brief Turn line wrapping in script editor on or off. + /// \param wrap Whether or not to wrap lines. + void wrapLines(bool wrap); + private slots: - void settingChanged (const CSMPrefs::Setting *setting); + /// \brief Update editor when related setting is changed. + /// \param setting Setting that was changed. + void settingChanged(const CSMPrefs::Setting *setting); void idListChanged(); From 776c715ccd419f1e77a8445e2ddd31692efcc942 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 29 Jan 2016 17:00:18 +0100 Subject: [PATCH 142/765] Move NoTraverseCallback to mwrender/util.hpp --- apps/openmw/mwrender/renderingmanager.cpp | 10 +--------- apps/openmw/mwrender/util.hpp | 11 ++++++++++- apps/openmw/mwrender/water.cpp | 11 +---------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 61ce626fde..c8d0925b0b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -42,6 +42,7 @@ #include "camera.hpp" #include "water.hpp" #include "terrainstorage.hpp" +#include "util.hpp" namespace MWRender { @@ -504,15 +505,6 @@ namespace MWRender mutable bool mDone; }; - - class NoTraverseCallback : public osg::NodeCallback - { - public: - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - } - }; - void RenderingManager::screenshot(osg::Image *image, int w, int h) { osg::ref_ptr rttCamera (new osg::Camera); diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index d078f0d982..815e345962 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWRENDER_UTIL_H #define OPENMW_MWRENDER_UTIL_H +#include #include #include @@ -16,9 +17,17 @@ namespace Resource namespace MWRender { - void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + // Node callback to entirely skip the traversal. + class NoTraverseCallback : public osg::NodeCallback + { + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + // no traverse() + } + }; } #endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1b19246f5b..b2bef71220 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -41,6 +41,7 @@ #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" +#include "util.hpp" namespace { @@ -210,16 +211,6 @@ private: osg::Plane mPlane; }; -// Node callback to entirely skip the traversal. -class NoTraverseCallback : public osg::NodeCallback -{ -public: - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - // no traverse() - } -}; - /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// The offset works around graphics artifacts that occured with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// Must be added as a Cull callback. From 2fed863941ca4e21ade52413d79869bc2e07ad12 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Jan 2016 21:17:56 +0100 Subject: [PATCH 143/765] Use the travis-ci status badge directly instead of the shields.io version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b55a89945e..6095fc3784 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OpenMW ====== -[![Build Status](https://img.shields.io/travis/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/ace13/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)](hgttps://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/ace13/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) 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. From ad748700d5a814cbbe576091897d5b028181a751 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Jan 2016 23:14:17 +0100 Subject: [PATCH 144/765] Update badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6095fc3784..9de814fd01 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OpenMW ====== -[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](hgttps://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/ace13/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/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) 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. From d28e7db65cce9b437f2594a0295a5e7fb3122aaa Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 31 Jan 2016 08:45:05 -0500 Subject: [PATCH 145/765] Fix tooltip spelling errors in WorldspaceWidget --- apps/opencs/view/render/worldspacewidget.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index af90af50cc..6ba6e95432 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -125,16 +125,16 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "First Person" "
    • Mouse-Look while holding the left button
    • " "
    • WASD movement keys
    • " - "
    • Mouse wheel moves the camera forawrd/backward
    • " - "
    • Stafing (also vertically) by holding the left mouse button and control
    • " + "
    • Mouse wheel moves the camera forward/backward
    • " + "
    • Strafing (also vertically) by holding the left mouse button and control
    • " "
    • Camera is held upright
    • " "
    • Hold shift to speed up movement
    • " "
    "); tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
    • Mouse-Look while holding the left button
    • " - "
    • Stafing (also vertically) via WASD or by holding the left mouse button and control
    • " - "
    • Mouse wheel moves the camera forawrd/backward
    • " + "
    • Strafing (also vertically) via WASD or by holding the left mouse button and control
    • " + "
    • Mouse wheel moves the camera forward/backward
    • " "
    • Roll camera with Q and E keys
    • " "
    • Hold shift to speed up movement
    • " "
    "); @@ -144,7 +144,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
  • Rotate around the centre point via WASD or by moving the mouse while holding the left button
  • " "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " "
  • Roll camera with Q and E keys
  • " - "
  • Stafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
  • " + "
  • Strafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
  • " "
  • Hold shift to speed up movement
  • " ""); From d12f24c32166c891f403accce717f9190dee8134 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 26 Jan 2016 19:41:05 +0100 Subject: [PATCH 146/765] Fix typo --- apps/opencs/model/prefs/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index fd777b1331..772047961c 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -140,7 +140,7 @@ void CSMPrefs::State::declare() declareEnum ("warnings", "Warning Mode", warningsNormal). addValue ("Ignore", "Do not report warning"). addValue (warningsNormal). - addValue ("Strcit", "Promote warning to an error"); + addValue ("Strict", "Promote warning to an error"); declareBool ("toolbar", "Show toolbar", true); declareInt ("compile-delay", "Delay between updating of source errors", 100). setTooltip ("Delay in milliseconds"). From 6b48acaf0e29232150c9beaf3faaf4fe97c44672 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 21:01:17 +0100 Subject: [PATCH 147/765] Don't optimize TriShapes with controllers (Fixes #3143) --- components/nifosg/nifloader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5c28bbc7e6..f4dae52c30 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -394,7 +394,10 @@ namespace NifOsg if (node->getDataVariance() == osg::Object::STATIC // For TriShapes, we can only collapse the node, but not completely remove it, // if the link to animated collision shapes is supposed to stay intact. - && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes)) + && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes) + // Don't optimize drawables with controllers, that creates issues when we want to deep copy controllers without deep copying the drawable that holds the controller. + // A deep copy of controllers may be needed to independently animate multiple copies of the same mesh. + && !node->getUpdateCallback()) { if (node->getNumParents() && nifNode->trafo.isIdentity()) { From 8360cccce7c2b95144b3eec9a607a253db7af63d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 21:02:48 +0100 Subject: [PATCH 148/765] Don't clone Drawable UpdateCallbacks twice The Drawable copy constructor takes care of that already. --- components/sceneutil/clone.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index a372b1ebd8..26f3f5c7cd 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -81,8 +81,6 @@ namespace SceneUtil #endif osg::Drawable* cloned = osg::clone(drawable, copyop); - if (cloned->getUpdateCallback()) - cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); return cloned; } if (dynamic_cast(drawable)) From c403a6b113a371bc469656486d5ea113bafb60aa Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 22:30:08 +0100 Subject: [PATCH 149/765] Don't apply constant magic effects to dead actors (Fixes #3174) --- apps/openmw/mwworld/inventorystore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 2c994f1467..318b7c5ec9 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -345,6 +345,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) mMagicEffects = MWMechanics::MagicEffects(); + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { if (*iter==end()) From c34314ae2671e90ded053a060f6dccc6e1aed283 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 22:52:28 +0100 Subject: [PATCH 150/765] When an actor dies purge all spell effects cast by that actor (Fixes #3175) --- apps/openmw/mwmechanics/activespells.cpp | 4 +--- apps/openmw/mwmechanics/actors.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 6a247622c5..c98f7d8293 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -274,9 +274,7 @@ namespace MWMechanics for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectId); - if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked - && it->second.mCasterActorId == casterActorId) + if (it->second.mCasterActorId == casterActorId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4046010d79..5d75cea35b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1209,7 +1209,7 @@ namespace MWMechanics ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; - // Make sure spell effects with CasterLinked flag are removed + // Make sure spell effects are removed for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) { MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); From 832eaae27b6933d256b4bb61a3224d4dc4bc0052 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 22:57:08 +0100 Subject: [PATCH 151/765] Do not apply effects with CasterLinked flag when there is no valid caster --- apps/openmw/mwmechanics/spellcasting.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6073076e00..4c51388356 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -378,6 +378,11 @@ namespace MWMechanics if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (caster.isEmpty() || !caster.getClass().isActor())) + continue; + // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth From dda4273349cb79ceafedd8a18678478aafc49f19 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Feb 2016 23:13:43 +0100 Subject: [PATCH 152/765] Actors that start the game as dead do not float to the surface (Fixes #3177) This has a minor bug (can you spot it?) that affects the vanilla engine as well, unfortunately not so simple to fix. --- apps/openmw/mwmechanics/character.cpp | 7 ++++++- apps/openmw/mwmechanics/character.hpp | 1 + apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db1b82ba6e..67707d0287 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -668,6 +668,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) + , mFloatToSurface(true) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) @@ -716,6 +717,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim // Set the death state, but don't play it yet // We will play it in the first frame, but only if no script set the skipAnim flag mDeathState = static_cast(CharState_Death1 + mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation()); + mFloatToSurface = false; } } else @@ -1864,7 +1866,7 @@ void CharacterController::update(float duration) { playDeath(1.f, mDeathState); } - + // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } @@ -1897,6 +1899,9 @@ void CharacterController::update(float duration) if (mSkipAnim) mAnimation->updateEffects(duration); + if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead()) + moved.z() = 1.0; + // Update movement if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5e43bc2b73..6db661ca54 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -160,6 +160,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener CharacterState mDeathState; std::string mCurrentDeath; + bool mFloatToSurface; CharacterState mHitState; std::string mCurrentHit; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 57bf7919a8..c99456ae52 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -289,8 +289,8 @@ namespace MWPhysics } } - // dead actors underwater will float to the surface - if (ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) + // dead actors underwater will float to the surface, if the CharacterController tells us to do so + if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; From 50ed061154322bf5d5b00b71c13930848d315b4b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Feb 2016 00:39:37 +0100 Subject: [PATCH 153/765] AiWander: reset mAllowedNodes on cell change (Fixes #3176, Fixes #3130) --- apps/openmw/mwmechanics/aiwander.cpp | 2 ++ apps/openmw/mwmechanics/aiwander.hpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a3fdc69ccc..8231e572e8 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -730,6 +730,8 @@ namespace MWMechanics const ESM::Pathgrid * pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + mAllowedNodes.clear(); + // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index b0fabfce3b..683c04bb0d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -120,16 +120,20 @@ namespace MWMechanics MWWorld::TimeStamp mStartTime; // allowed pathgrid nodes based on mDistance from the spawn point + // in local coordinates of mCell + // FIXME: move to AiWanderStorage std::vector mAllowedNodes; void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell); + // FIXME: move to AiWanderStorage ESM::Pathgrid::Point mCurrentNode; bool mTrimCurrentNode; void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); + // FIXME: move to AiWanderStorage // ObstacleCheck mObstacleCheck; float mDoorCheckDuration; int mStuckCount; From 59d2de118f8f9f1bc6493ab3eab10c0d35d26448 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Feb 2016 00:50:21 +0100 Subject: [PATCH 154/765] Avoid directly iterating the actor map (Fixes #3173) --- apps/openmw/mwmechanics/actors.cpp | 37 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5d75cea35b..f9c70e7ae6 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -982,8 +982,11 @@ namespace MWMechanics /// \todo move update logic to Actor class where appropriate + // make a copy of the map to avoid invalidated iterators when an actor moves during the update + PtrActorMap actors = mActors; + // AI and magic effects update - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) { bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrProcessingDistance; @@ -996,6 +999,11 @@ namespace MWMechanics if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { updateActor(iter->first, duration); + if (MWBase::Environment::get().getWorld()->hasCellChanged()) + { + return; // for now abort update of the old cell when cell changes by teleportation magic effect + // a better solution might be to apply cell changes at the end of the frame + } if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) { if (timerUpdateAITargets == 0) @@ -1003,7 +1011,7 @@ namespace MWMechanics if (iter->first != player) adjustCommandedActor(iter->first); - for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + for(PtrActorMap::iterator it(actors.begin()); it != actors.end(); ++it) { if (it->first == iter->first || iter->first == player) // player is not AI-controlled continue; @@ -1015,7 +1023,7 @@ namespace MWMechanics float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; - for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + for(PtrActorMap::iterator it(actors.begin()); it != actors.end(); ++it) { if (it->first == iter->first) continue; @@ -1050,12 +1058,12 @@ namespace MWMechanics // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // so updating VFX immediately after that would just remove the particle effects instantly. // There needs to be a magic effect update in between. - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) iter->second->getCharacterController()->updateContinuousVfx(); // Animation/movement update CharacterController* playerCharacter = NULL; - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) { if (iter->first != player && (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() @@ -1065,20 +1073,10 @@ namespace MWMechanics if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) iter->second->getCharacterController()->skipAnim(); - // Handle player last, in case a cell transition occurs by casting a teleportation spell - // (would invalidate the iterator) - if (iter->first == getPlayer()) - { - playerCharacter = iter->second->getCharacterController(); - continue; - } iter->second->getCharacterController()->update(duration); } - if (playerCharacter) - playerCharacter->update(duration); - - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1136,7 +1134,7 @@ namespace MWMechanics bool detected = false; - for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) { if (iter->first == player) // not the player continue; @@ -1179,7 +1177,8 @@ namespace MWMechanics void Actors::killDeadActors() { - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + PtrActorMap actors = mActors; + for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1210,7 +1209,7 @@ namespace MWMechanics ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; // Make sure spell effects are removed - for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + for (PtrActorMap::iterator iter2(actors.begin());iter2 != actors.end();++iter2) { MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); spells.purge(stats.getActorId()); From 145756c0a1a39d378f09a981c7b05d3dc0a07368 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Feb 2016 15:55:19 +0100 Subject: [PATCH 155/765] Partly revert "Avoid directly iterating the actor map (Fixes #3173)" Caused issues when a summoned creature is removed as part of the magic effect update. Fixes #3178 --- apps/openmw/mwmechanics/actors.cpp | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f9c70e7ae6..70aab95dfe 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -982,11 +982,8 @@ namespace MWMechanics /// \todo move update logic to Actor class where appropriate - // make a copy of the map to avoid invalidated iterators when an actor moves during the update - PtrActorMap actors = mActors; - // AI and magic effects update - for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrProcessingDistance; @@ -1011,7 +1008,7 @@ namespace MWMechanics if (iter->first != player) adjustCommandedActor(iter->first); - for(PtrActorMap::iterator it(actors.begin()); it != actors.end(); ++it) + for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first || iter->first == player) // player is not AI-controlled continue; @@ -1023,7 +1020,7 @@ namespace MWMechanics float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; - for(PtrActorMap::iterator it(actors.begin()); it != actors.end(); ++it) + for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first) continue; @@ -1058,12 +1055,12 @@ namespace MWMechanics // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // so updating VFX immediately after that would just remove the particle effects instantly. // There needs to be a magic effect update in between. - for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) iter->second->getCharacterController()->updateContinuousVfx(); // Animation/movement update CharacterController* playerCharacter = NULL; - for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first != player && (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() @@ -1073,10 +1070,20 @@ namespace MWMechanics if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) iter->second->getCharacterController()->skipAnim(); + // Handle player last, in case a cell transition occurs by casting a teleportation spell + // (would invalidate the iterator) + if (iter->first == getPlayer()) + { + playerCharacter = iter->second->getCharacterController(); + continue; + } iter->second->getCharacterController()->update(duration); } - for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + if (playerCharacter) + playerCharacter->update(duration); + + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1134,7 +1141,7 @@ namespace MWMechanics bool detected = false; - for (PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first == player) // not the player continue; @@ -1177,8 +1184,7 @@ namespace MWMechanics void Actors::killDeadActors() { - PtrActorMap actors = mActors; - for(PtrActorMap::iterator iter(actors.begin()); iter != actors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1209,7 +1215,7 @@ namespace MWMechanics ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; // Make sure spell effects are removed - for (PtrActorMap::iterator iter2(actors.begin());iter2 != actors.end();++iter2) + for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) { MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); spells.purge(stats.getActorId()); From 7aeafd3bb9403104134c0a22b32925fafd9ec976 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Jan 2016 14:51:37 +0100 Subject: [PATCH 156/765] Revert "Apply the AiTravel maxRange to AiEscort as well (Fixes #2697)" This reverts commit 1f543b4d79744886aa9a03ad7fcff6d97dc5f70c. --- apps/openmw/mwmechanics/aiescort.cpp | 3 --- apps/openmw/mwmechanics/aipackage.hpp | 9 --------- apps/openmw/mwmechanics/aitravel.cpp | 12 ++++++++++++ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index baf4f1be77..fffab8d77d 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -75,9 +75,6 @@ namespace MWMechanics return true; } - if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) - return false; - if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 76f89dff4e..07df933e27 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -97,19 +97,10 @@ namespace MWMechanics ESM::Pathgrid::Point mPrevDest; - bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) const - { - // Maximum travel distance for vanilla compatibility. - // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. - // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1 - pos2).length2() <= 7168*7168; - } - private: bool isNearInactiveCell(const ESM::Position& actorPos); }; - } #endif diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index c29861f4ee..1585a3007f 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -13,6 +13,18 @@ #include "movement.hpp" #include "creaturestats.hpp" +namespace +{ + +bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) +{ + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. + // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + return (pos1 - pos2).length2() <= 7168*7168; +} + +} namespace MWMechanics { From d3b76b70063da765b107a766142a681bd7ad5f73 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Jan 2016 14:51:42 +0100 Subject: [PATCH 157/765] Don't stack Ai packages (Fixes #3101, Fixes #3080, Fixes #2697) --- apps/openmw/mwmechanics/aiavoiddoor.hpp | 3 + apps/openmw/mwmechanics/aicombat.hpp | 3 + apps/openmw/mwmechanics/aipackage.cpp | 10 ++ apps/openmw/mwmechanics/aipackage.hpp | 8 ++ apps/openmw/mwmechanics/aipursue.hpp | 3 + apps/openmw/mwmechanics/aisequence.cpp | 137 +++++++++++++----------- 6 files changed, 101 insertions(+), 63 deletions(-) diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 9d63c63e08..41c41b1f57 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -26,6 +26,9 @@ namespace MWMechanics virtual unsigned int getPriority() const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: float mDuration; MWWorld::ConstPtr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 93d6305291..2b364001ff 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -53,6 +53,9 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + protected: virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index cb2b002f6c..58ba7dfe8e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -35,6 +35,16 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const return false; } +bool MWMechanics::AiPackage::canCancel() const +{ + return true; +} + +bool MWMechanics::AiPackage::shouldCancelPreviousAi() const +{ + return true; +} + MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 07df933e27..72bb4487ce 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -39,6 +39,8 @@ namespace MWMechanics TypeIdEscort = 2, TypeIdFollow = 3, TypeIdActivate = 4, + + // These 3 are not really handled as Ai Packages in the MW engine TypeIdCombat = 5, TypeIdPursue = 6, TypeIdAvoidDoor = 7 @@ -78,6 +80,12 @@ namespace MWMechanics /// Return true if the actor should follow the target through teleport doors (default false) virtual bool followTargetThroughDoors() const; + /// Can this Ai package be canceled? (default true) + virtual bool canCancel() const; + + /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? + virtual bool shouldCancelPreviousAi() const; + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 813b87cff0..cb93e9636a 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -38,6 +38,9 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d55dc240e0..04ac96b11b 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -153,83 +153,86 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if(actor != getPlayer()) { - if (!mPackages.empty()) + if (mPackages.empty()) { - MWMechanics::AiPackage* package = mPackages.front(); - mLastAiPackage = package->getTypeId(); + mLastAiPackage = -1; + return; + } - // if active package is combat one, choose nearest target - if (mLastAiPackage == AiPackage::TypeIdCombat) + MWMechanics::AiPackage* package = mPackages.front(); + mLastAiPackage = package->getTypeId(); + + // if active package is combat one, choose nearest target + if (mLastAiPackage == AiPackage::TypeIdCombat) + { + std::list::iterator itActualCombat; + + float nearestDist = std::numeric_limits::max(); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { - std::list::iterator itActualCombat; + if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - float nearestDist = std::numeric_limits::max(); - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + MWWorld::Ptr target = static_cast(*it)->getTarget(); - for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) { - if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - - MWWorld::Ptr target = static_cast(*it)->getTarget(); - - // target disappeared (e.g. summoned creatures) - if (target.isEmpty()) - { - delete *it; - it = mPackages.erase(it); - } - else - { - const ESM::Position &targetPos = target.getRefData().getPosition(); - - float distTo = (targetPos.asVec3() - vActorPos).length(); - - // Small threshold for changing target - if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 50.f); - - if (distTo < nearestDist) - { - nearestDist = distTo; - itActualCombat = it; - } - ++it; - } + delete *it; + it = mPackages.erase(it); } - - if (!mPackages.empty()) + else { - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + const ESM::Position &targetPos = target.getRefData().getPosition(); + + float distTo = (targetPos.asVec3() - vActorPos).length(); + + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 50.f); + + if (distTo < nearestDist) { - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + nearestDist = distTo; + itActualCombat = it; } - - package = mPackages.front(); - mLastAiPackage = package->getTypeId(); - } - else - { - mDone = true; - return; + ++it; } } - if (package->execute (actor,characterController,state,duration)) + if (!mPackages.empty()) { - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - std::list::iterator toRemove = - std::find(mPackages.begin(), mPackages.end(), package); - mPackages.erase(toRemove); - delete package; - mDone = true; + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + // move combat package with nearest target to the front + mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + } + + package = mPackages.front(); + mLastAiPackage = package->getTypeId(); } else { - mDone = false; + mDone = true; + return; } } + + if (package->execute (actor,characterController,state,duration)) + { + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + std::list::iterator toRemove = + std::find(mPackages.begin(), mPackages.end(), package); + mPackages.erase(toRemove); + delete package; + mDone = true; + } + else + { + mDone = false; + } } } @@ -251,11 +254,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) // Notify AiWander of our current position so we can return to it after combat finished for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) { - if((*iter)->getTypeId() == AiPackage::TypeIdPursue && package.getTypeId() == AiPackage::TypeIdPursue - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) - { - return; // target is already pursued - } if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) { @@ -266,6 +264,19 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) } } + // remove previous packages if required + if (package.shouldCancelPreviousAi()) + { + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if((*it)->canCancel()) + it = mPackages.erase(it); + else + ++it; + } + } + + // insert new package in correct place depending on priority for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if((*it)->getPriority() <= package.getPriority()) From b9d1d6144a337e7d54eb4c9b4f1af785ffe0ed31 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Feb 2016 22:49:48 +0100 Subject: [PATCH 158/765] Don't reveal unknown potion effects in alchemy window (Fixes #3146) --- apps/openmw/mwclass/potion.cpp | 15 +++------------ apps/openmw/mwgui/alchemywindow.cpp | 4 ++++ apps/openmw/mwmechanics/alchemy.cpp | 10 ++++++++++ apps/openmw/mwmechanics/alchemy.hpp | 3 +++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 40b4d62342..c26b925f6f 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -20,7 +20,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/alchemy.hpp" namespace MWClass { @@ -124,17 +124,8 @@ namespace MWClass // hide effects the player doesnt know about MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); - int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); - int i=0; - static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); - for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) - { - it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue) - || (i <= 3 && alchemySkill >= fWortChanceValue*2); - ++i; - } + for (unsigned int i=0; i effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; + unsigned int effectIndex=0; for (std::set::iterator it = effectIds.begin(); it != effectIds.end(); ++it) { Widgets::SpellEffectParams params; @@ -228,7 +229,10 @@ namespace MWGui params.mIsConstant = true; params.mNoTarget = true; + params.mKnown = mAlchemy->knownEffect(effectIndex, MWBase::Environment::get().getWorld()->getPlayerPtr()); + list.push_back(params); + ++effectIndex; } while (mEffectsBox->getChildCount()) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index d39c881506..d02376d232 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -451,6 +451,16 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const return mEffects.end(); } +bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) +{ + MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); + int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) + || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2); +} + MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name) { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index caba26f149..f351881e0d 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -80,6 +80,9 @@ namespace MWMechanics public: + static bool knownEffect (unsigned int potionEffectIndex, const MWWorld::Ptr& npc); + ///< Does npc have sufficient alchemy skill to know about this potion effect? + void setAlchemist (const MWWorld::Ptr& npc); ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that /// there is no alchemist (alchemy session has ended). From 5878291064e73b3c6a0a59c16db92031d45a076e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 14:40:21 +0100 Subject: [PATCH 159/765] Fix the path correction for animation sources provided in NPC record (Fixes #2444) --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index a032896a71..c8d7c79c5d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -473,7 +473,7 @@ void NpcAnimation::updateNpcBase() else if(!mNpc->isMale() && !isBeast) addAnimSource("meshes\\xbase_anim_female.nif"); if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\x"+mNpc->mModel); + addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS())); } } else From 74c18f532e30455a440dc87feac329ce14be1941 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 14:40:59 +0100 Subject: [PATCH 160/765] Fix comment --- apps/openmw/mwrender/animation.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 213e33f673..992462e1f1 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -292,9 +292,10 @@ protected: */ void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); - /* Adds the keyframe controllers in the specified model as a new animation source. Note that - * the filename portion of the provided model name will be prepended with 'x', and the .nif - * extension will be replaced with .kf. */ + /** Adds the keyframe controllers in the specified model as a new animation source. Note that the .nif + * file extension will be replaced with .kf. + * @note Later added animation sources have the highest priority when it comes to finding a particular animation. + */ void addAnimSource(const std::string &model); /** Adds an additional light to the given node using the specified ESM record. */ From aa9905b0eb2fa0ae6425ca5d192af67931b5620f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 15:24:28 +0100 Subject: [PATCH 161/765] Do not crash when the water normal map is missing (Fixes #3179) --- apps/openmw/mwrender/water.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b2bef71220..c7f2c9cc6e 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -578,12 +578,13 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R osg::ref_ptr fragmentShader (readShader(osg::Shader::FRAGMENT, mResourcePath + "/shaders/water_fragment.glsl", defineMap)); osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); + if (normalMap->getImage()) + normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - normalMap->getImage()->flipVertical(); osg::ref_ptr shaderStateset = new osg::StateSet; shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); From 187d2bccda53da425f49bc7e652bee29c63822cf Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 16:06:25 +0100 Subject: [PATCH 162/765] Warn about adding a local script twice (Bug #2806) --- apps/openmw/mwworld/localscripts.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 2004a2ff39..374f5c632a 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -110,6 +110,14 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { ptr.getRefData().setLocals (*script); + for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) + if (iter->second==ptr) + { + std::cout << "warning, tried to add local script twice for " << ptr.getCellRef().getRefId() << std::endl; + remove(ptr); + break; + } + mScripts.push_back (std::make_pair (scriptName, ptr)); } catch (const std::exception& exception) From cc3563359e3ab2d7e3b02feb0b62533ed03e3d64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 16:09:20 +0100 Subject: [PATCH 163/765] Refactor local script iteration (Fixes #2806, Fixes #3108) This should be much safer. Don't use recursion. Don't fail if mIgnore happens to be in the list twice. Don't rely on preconditions / assertions. --- apps/openmw/engine.cpp | 6 ++---- apps/openmw/mwworld/localscripts.cpp | 28 ++++++++-------------------- apps/openmw/mwworld/localscripts.hpp | 8 +++----- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3fcd46f7ca..f347bc3506 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -69,11 +69,9 @@ void OMW::Engine::executeLocalScripts() MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); - - while (!localScripts.isFinished()) + std::pair script; + while (localScripts.getNext(script)) { - std::pair script = localScripts.getNext(); - MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); mEnvironment.getScriptManager()->run (script.first, interpreterContext); diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 374f5c632a..7ccc213c45 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -76,32 +76,20 @@ void MWWorld::LocalScripts::startIteration() mIter = mScripts.begin(); } -bool MWWorld::LocalScripts::isFinished() const +bool MWWorld::LocalScripts::getNext(std::pair& script) { - if (mIter==mScripts.end()) - return true; - - if (!mIgnore.isEmpty() && mIter->second==mIgnore) + while (mIter!=mScripts.end()) { - std::list >::iterator iter = mIter; - return ++iter==mScripts.end(); + std::list >::iterator iter = mIter++; + if (mIgnore.isEmpty() || iter->second!=mIgnore) + { + script = *iter; + return true; + } } - return false; } -std::pair MWWorld::LocalScripts::getNext() -{ - assert (!isFinished()); - - std::list >::iterator iter = mIter++; - - if (mIgnore.isEmpty() || iter->second!=mIgnore) - return *iter; - - return getNext(); -} - void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { if (const ESM::Script *script = mStore.get().search (scriptName)) diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 6ef4633a1d..6c2118ea8e 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -31,11 +31,9 @@ namespace MWWorld void startIteration(); ///< Set the iterator to the begin of the script list. - bool isFinished() const; - ///< Is iteration finished? - - std::pair getNext(); - ///< Get next local script (must not be called if isFinished()) + bool getNext(std::pair& script); + ///< Get next local script + /// @return Did we get a script? void add (const std::string& scriptName, const Ptr& ptr); ///< Add script to collection of active local scripts. From 06ed20abf8e9b59384c3221dd2c390d205296ae8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 18:53:38 +0100 Subject: [PATCH 164/765] Use the initial pose of a MorphGeometry for object placement (Fixes #3136) --- components/nifosg/nifloader.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f4dae52c30..280985f7ef 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -999,7 +999,7 @@ namespace NifOsg continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { - geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); + geometry = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController( static_cast(ctrl.getPtr())->data.getPtr()); @@ -1010,9 +1010,10 @@ namespace NifOsg } if (!geometry.get()) + { geometry = new osg::Geometry; - - triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + } #if OSG_VERSION_LESS_THAN(3,3,3) osg::ref_ptr geode (new osg::Geode); @@ -1046,7 +1047,7 @@ namespace NifOsg #endif } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); @@ -1056,6 +1057,8 @@ namespace NifOsg morphGeom->setUpdateCallback(NULL); morphGeom->setCullCallback(new UpdateMorphGeometry); + triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); + const std::vector& morphs = morpher->data.getPtr()->mMorphs; if (!morphs.size()) return morphGeom; @@ -1096,6 +1099,10 @@ namespace NifOsg box.expandBy(vertBounds[i]); } + // For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box + morphGeom->getBoundingBox(); + + // Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); return morphGeom; From 2eda495f8940a76c5dbd31052d6b54ccbd3e9b76 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 19:05:15 +0100 Subject: [PATCH 165/765] Build fix for OSG 3.2 --- components/nifosg/nifloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 280985f7ef..79f8379532 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1100,7 +1100,7 @@ namespace NifOsg } // For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box - morphGeom->getBoundingBox(); + morphGeom->getBound(); // Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); From 69c2c4fcc170d4b82b5de0cdc5f36df2dbef0994 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Feb 2016 19:12:46 +0100 Subject: [PATCH 166/765] updateMergedRefs before reading MVRF tags (Fixes #3161) --- apps/openmw/mwworld/cellstore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 5b53643e89..5c8d07f86c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -807,6 +807,10 @@ namespace MWWorld } } + // Do another update here to make sure objects referred to by MVRF tags can be found + // This update is only needed for old saves that used the old copy&delete way of moving objects + updateMergedRefs(); + while (reader.isNextSub("MVRF")) { reader.cacheSubName(); @@ -848,8 +852,6 @@ namespace MWWorld moveTo(MWWorld::Ptr(movedRef, this), otherCell); } - - updateMergedRefs(); } bool operator== (const CellStore& left, const CellStore& right) From 41ebf62fb13785fd9b2be138aa694bfa63dcf8b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 00:25:14 +0100 Subject: [PATCH 167/765] Accept a const CellStore in getNorthVector --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 06a8ffa352..c2a7de72b2 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -141,7 +141,7 @@ namespace MWBase virtual bool isCellQuasiExterior() const = 0; - virtual osg::Vec2f getNorthVector (MWWorld::CellStore* cell) = 0; + virtual osg::Vec2f getNorthVector (const MWWorld::CellStore* cell) = 0; ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 37743bceb0..a556aabf7c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1710,9 +1710,9 @@ namespace MWWorld mWeatherManager->modRegion(regionid, chances); } - osg::Vec2f World::getNorthVector (CellStore* cell) + osg::Vec2f World::getNorthVector (const CellStore* cell) { - MWWorld::Ptr northmarker = cell->search("northmarker"); + MWWorld::ConstPtr northmarker = cell->searchConst("northmarker"); if (northmarker.isEmpty()) return osg::Vec2f(0, 1); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a9dce46e87..487d4ed04d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -227,7 +227,7 @@ namespace MWWorld virtual bool isCellQuasiExterior() const; - virtual osg::Vec2f getNorthVector (CellStore* cell); + virtual osg::Vec2f getNorthVector (const CellStore* cell); ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); From 300379617e9a604e20dff39962f18ab8b33b7494 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 00:25:47 +0100 Subject: [PATCH 168/765] Accept a const CellStore in findInteriorPositionInWorldSpace --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c2a7de72b2..119ce6b217 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -489,7 +489,7 @@ namespace MWBase // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const = 0; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) = 0; + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a556aabf7c..9064dc94cd 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2654,7 +2654,7 @@ namespace MWWorld } } - bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) + bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) return false; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 487d4ed04d..8c56443f79 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -592,7 +592,7 @@ namespace MWWorld // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result); + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result); /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case From bd655c20fd2a7c23b83106a5349f718972827883 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 00:39:52 +0100 Subject: [PATCH 169/765] Refactor local map updates We don't need the delay any more because the rendering itself is part of the normal rendering traversal - so it's delayed anyway. Don't request maps that we're not actually using (i.e. with cell grid sizes higher than the default 3, we were rendering more maps than the map window could show). --- apps/openmw/mwbase/windowmanager.hpp | 3 +-- apps/openmw/mwgui/mapwindow.cpp | 20 +++++++++++++++++ apps/openmw/mwgui/mapwindow.hpp | 6 +++++ apps/openmw/mwgui/windowmanagerimp.cpp | 20 ++++++++--------- apps/openmw/mwgui/windowmanagerimp.hpp | 3 +-- apps/openmw/mwrender/localmap.cpp | 16 +++++++------ apps/openmw/mwrender/localmap.hpp | 6 ++--- apps/openmw/mwworld/scene.cpp | 31 +------------------------- apps/openmw/mwworld/scene.hpp | 2 -- 9 files changed, 51 insertions(+), 56 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f364ada7ac..2d4c0e2cab 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -176,7 +176,7 @@ namespace MWBase virtual void updateSkillArea() = 0; ///< update display of skills, factions, birth sign, reputation and bounty - virtual void changeCell(MWWorld::CellStore* cell) = 0; + virtual void changeCell(const MWWorld::CellStore* cell) = 0; ///< change the active cell virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; @@ -354,7 +354,6 @@ namespace MWBase virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0; virtual std::string correctTexturePath(const std::string& path) = 0; - virtual void requestMap(std::set cells) = 0; virtual void removeCell(MWWorld::CellStore* cell) = 0; virtual void writeFog(MWWorld::CellStore* cell) = 0; }; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 89f4d8cf37..469564d9d2 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -454,6 +454,26 @@ namespace MWGui updateCustomMarkers(); } + void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) + { + std::set cells; + if (!cell->isExterior()) + cells.insert(cell); + else + { + for (int dX=-1; dX<2; ++dX) + { + for (int dY=-1; dY<2; ++dY) + { + const MWWorld::CellStore* gridCell = MWBase::Environment::get().getWorld()->getExterior (cell->getCell()->getGridX()+dX, cell->getCell()->getGridY()+dY); + cells.insert(gridCell); + } + } + } + + mLocalMapRender->requestMap(cells); + } + void LocalMapBase::redraw() { // Redraw children in proper order diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 227a9e3f96..96fd3c9942 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -23,6 +23,11 @@ namespace ESM class ESMWriter; } +namespace MWWorld +{ + class CellStore; +} + namespace Loading { class Listener; @@ -67,6 +72,7 @@ namespace MWGui void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); + void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index f151bee80b..90c0494f6d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -926,7 +926,7 @@ namespace MWGui if (!mLocalMapRender) return; - MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); @@ -938,10 +938,9 @@ namespace MWGui if (!player.getCell()->isExterior()) { - mMap->setActiveCell(x, y, true); - mHud->setActiveCell(x, y, true); + setActiveMap(x, y, true); } - // else: need to know the current grid center, call setActiveCell from MWWorld::Scene + // else: need to know the current grid center, call setActiveMap from changeCell mMap->setPlayerDir(playerdirection.x(), playerdirection.y()); mMap->setPlayerPos(x, y, u, v); @@ -1007,8 +1006,10 @@ namespace MWGui mDebugWindow->onFrame(frameDuration); } - void WindowManager::changeCell(MWWorld::CellStore* cell) + void WindowManager::changeCell(const MWWorld::CellStore* cell) { + mMap->requestMapRender(cell); + std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); mMap->setCellName( name ); @@ -1020,6 +1021,8 @@ namespace MWGui mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); + + setActiveMap(cell->getCell()->getGridX(), cell->getCell()->getGridY(), false); } else { @@ -1032,6 +1035,8 @@ namespace MWGui else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); + + setActiveMap(0, 0, true); } } @@ -2078,11 +2083,6 @@ namespace MWGui tex->unlock(); } - void WindowManager::requestMap(std::set cells) - { - mLocalMapRender->requestMap(cells); - } - void WindowManager::removeCell(MWWorld::CellStore *cell) { mLocalMapRender->removeCell(cell); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 6edb4660cf..3cdba8a87f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -204,7 +204,7 @@ namespace MWGui virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group. virtual void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty - virtual void changeCell(MWWorld::CellStore* cell); ///< change the active cell + virtual void changeCell(const MWWorld::CellStore* cell); ///< change the active cell virtual void setFocusObject(const MWWorld::Ptr& focus); virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); @@ -375,7 +375,6 @@ namespace MWGui virtual std::string correctBookartPath(const std::string& path, int width, int height); virtual std::string correctTexturePath(const std::string& path); - void requestMap(std::set cells); void removeCell(MWWorld::CellStore* cell); void writeFog(MWWorld::CellStore* cell); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 200f484b57..4efcf5d1b4 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -229,11 +229,11 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int segment.mMapTexture = texture; } -void LocalMap::requestMap(std::set cells) +void LocalMap::requestMap(std::set cells) { - for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) + for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) { - MWWorld::CellStore* cell = *it; + const MWWorld::CellStore* cell = *it; if (cell->isExterior()) requestExteriorMap(cell); else @@ -296,7 +296,7 @@ void LocalMap::cleanupCameras() mCamerasPendingRemoval.clear(); } -void LocalMap::requestExteriorMap(MWWorld::CellStore* cell) +void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) { mInterior = false; @@ -321,7 +321,7 @@ void LocalMap::requestExteriorMap(MWWorld::CellStore* cell) } } -void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) +void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); @@ -375,6 +375,7 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) // If they changed by too much (for bounds, < padding is considered acceptable) then parts of the interior might not // be covered by the map anymore. // The following code detects this, and discards the CellStore's fog state if it needs to. + bool cellHasValidFog = false; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); @@ -390,13 +391,14 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) { // Nuke it - cell->setFog(NULL); + cellHasValidFog = false; } else { // Looks sane, use it mBounds = osg::BoundingBox(newMin, newMax); mAngle = fog->mNorthMarkerAngle; + cellHasValidFog = true; } } @@ -434,7 +436,7 @@ void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) MapSegment& segment = mSegments[std::make_pair(x,y)]; if (!segment.mFogOfWarImage) { - if (!cell->getFog()) + if (!cellHasValidFog) segment.initFogOfWar(); else { diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 59165013d6..52f570b0b1 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -52,7 +52,7 @@ namespace MWRender /** * Request a map render for the given cells. Render textures will be immediately created and can be retrieved with the getMapTexture function. */ - void requestMap (std::set cells); + void requestMap (std::set cells); /** * Remove map and fog textures for the given cell. @@ -146,8 +146,8 @@ namespace MWRender float mAngle; const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); - void requestExteriorMap(MWWorld::CellStore* cell); - void requestInteriorMap(MWWorld::CellStore* cell); + void requestExteriorMap(const MWWorld::CellStore* cell); + void requestInteriorMap(const MWWorld::CellStore* cell); osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); void setupRenderToTexture(osg::ref_ptr camera, int x, int y); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 15b3c057bc..dc93c3b1d7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -179,27 +179,6 @@ namespace MWWorld void Scene::update (float duration, bool paused) { - if (mNeedMapUpdate) - { - // Note: exterior cell maps must be updated, even if they were visited before, because the set of surrounding cells might be different - // (and objects in a different cell can "bleed" into another cells map if they cross the border) - std::set cellsToUpdate; - for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) - { - cellsToUpdate.insert(*active); - } - MWBase::Environment::get().getWindowManager()->requestMap(cellsToUpdate); - - mNeedMapUpdate = false; - - if (mCurrentCell->isExterior()) - { - int cellX, cellY; - getGridCenter(cellX, cellY); - MWBase::Environment::get().getWindowManager()->setActiveMap(cellX,cellY,false); - } - } - mRendering.update (duration, paused); } @@ -410,10 +389,6 @@ namespace MWWorld mCellChanged = true; - // Delay the map update until scripts have been given a chance to run. - // If we don't do this, objects that should be disabled will still appear on the map. - mNeedMapUpdate = true; - mRendering.getResourceSystem()->clearCache(); } @@ -449,7 +424,7 @@ namespace MWWorld } Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) - : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNeedMapUpdate(false) + : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) { } @@ -527,10 +502,6 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); - // Delay the map update until scripts have been given a chance to run. - // If we don't do this, objects that should be disabled will still appear on the map. - mNeedMapUpdate = true; - mRendering.getResourceSystem()->clearCache(); } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 439c8d72c3..c6de3ebdf7 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -58,8 +58,6 @@ namespace MWWorld MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; - bool mNeedMapUpdate; - void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center From 7d647088ab577357d48e437ef6579a60102e5006 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 01:18:44 +0100 Subject: [PATCH 170/765] Make the local map cell distance configurable --- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/mapwindow.cpp | 59 ++++++++++++++++--------------- apps/openmw/mwgui/mapwindow.hpp | 5 ++- apps/openmw/mwrender/localmap.cpp | 5 +-- apps/openmw/mwrender/localmap.hpp | 2 ++ files/settings-default.cfg | 5 +++ 6 files changed, 46 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 8d6ce6beb4..b5c8dc8bb6 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -159,7 +159,7 @@ namespace MWGui getWidget(mCrosshair, "Crosshair"); - LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map")); + LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map")); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 469564d9d2..3b5f0d8aab 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -181,20 +181,22 @@ namespace MWGui mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = mapWidgetSize; + mCellDistance = cellDistance; + mNumCells = cellDistance * 2 + 1; - mLocalMap->setCanvasSize(mMapWidgetSize*3, mMapWidgetSize*3); + mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); - for (int mx=0; mx<3; ++mx) + for (int mx=0; mxcreateWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), @@ -231,13 +233,13 @@ namespace MWGui void LocalMapBase::applyFogOfWar() { TextureVector fogTextures; - for (int mx=0; mx<3; ++mx) + for (int mx=0; mx(nX * mMapWidgetSize + (1 + cellDx) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize - (cellDy-1) * mMapWidgetSize)); + widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + cellDx) * mMapWidgetSize), + static_cast(nY * mMapWidgetSize + (mCellDistance - cellDy) * mMapWidgetSize)); } else { @@ -297,8 +299,8 @@ namespace MWGui markerPos.cellY = cellY; // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (1 + (cellX - mCurX)) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (1-(cellY-mCurY)) * mMapWidgetSize)); + widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + (cellX - mCurX)) * mMapWidgetSize), + static_cast(nY * mMapWidgetSize + (mCellDistance - (cellY - mCurY)) * mMapWidgetSize)); } markerPos.nX = nX; @@ -312,9 +314,9 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); mCustomMarkerWidgets.clear(); - for (int dX = -1; dX <= 1; ++dX) + for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { - for (int dY =-1; dY <= 1; ++dY) + for (int dY =-mCellDistance; dY <= mCellDistance; ++dY) { ESM::CellId cellId; cellId.mPaged = !mInterior; @@ -372,14 +374,14 @@ namespace MWGui // Update the map textures TextureVector textures; - for (int mx=0; mx<3; ++mx) + for (int mx=0; mx texture = mLocalMapRender->getMapTexture(mapX, mapY); if (texture) @@ -406,9 +408,9 @@ namespace MWGui } else { - for (int dX=-1; dX<2; ++dX) + for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) { - for (int dY=-1; dY<2; ++dY) + for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) { MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); world->getDoorMarkers(cell, doors); @@ -461,9 +463,9 @@ namespace MWGui cells.insert(cell); else { - for (int dX=-1; dX<2; ++dX) + for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) { - for (int dY=-1; dY<2; ++dY) + for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) { const MWWorld::CellStore* gridCell = MWBase::Environment::get().getWorld()->getExterior (cell->getCell()->getGridX()+dX, cell->getCell()->getGridY()+dY); cells.insert(gridCell); @@ -482,7 +484,7 @@ namespace MWGui void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { - MyGUI::IntPoint pos(static_cast(mMapWidgetSize + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize + ny*mMapWidgetSize - 16)); + MyGUI::IntPoint pos(static_cast(mMapWidgetSize * mCellDistance + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize * mCellDistance + ny*mMapWidgetSize - 16)); pos.left += (cellX - mCurX) * mMapWidgetSize; pos.top -= (cellY - mCurY) * mMapWidgetSize; @@ -663,7 +665,8 @@ namespace MWGui mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map")); } + LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map")); + } void MapWindow::onNoteEditOk() { @@ -707,8 +710,8 @@ namespace MWGui MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); - int x = int(widgetPos.left/float(mMapWidgetSize))-1; - int y = (int(widgetPos.top/float(mMapWidgetSize))-1)*-1; + int x = int(widgetPos.left/float(mMapWidgetSize))-mCellDistance; + int y = (int(widgetPos.top/float(mMapWidgetSize))-mCellDistance)*-1; float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); x += mCurX; diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 96fd3c9942..773522903a 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -68,7 +68,7 @@ namespace MWGui public: LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize, int cellDistance); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); @@ -116,6 +116,9 @@ namespace MWGui int mMapWidgetSize; + int mNumCells; // for convenience, mCellDistance * 2 + 1 + int mCellDistance; + // Stores markers that were placed by a player. May be shared between multiple map views. CustomMarkerCollection& mCustomMarkers; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 4efcf5d1b4..fd224ba415 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -72,6 +72,7 @@ LocalMap::LocalMap(osgViewer::Viewer* viewer) : mViewer(viewer) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(8192.f) + , mCellDistance(Settings::Manager::getInt("local map cell distance", "Map")) , mAngle(0.f) , mInterior(false) { @@ -533,9 +534,9 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) // change the affected fog of war textures (in a 3x3 grid around the player) - for (int mx = -1; mx<2; ++mx) + for (int mx = -mCellDistance; mx<=mCellDistance; ++mx) { - for (int my = -1; my<2; ++my) + for (int my = -mCellDistance; my<=mCellDistance; ++my) { // is this texture affected at all? bool affected = false; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 52f570b0b1..d946f4c2b6 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -143,6 +143,8 @@ namespace MWRender // size of a map segment (for exteriors, 1 cell) float mMapWorldSize; + int mCellDistance; + float mAngle; const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e4a8387b4b..dddf1a29b9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -58,6 +58,11 @@ local map resolution = 256 # Size of local map in GUI window in pixels. (e.g. 256 to 1024). local map widget size = 512 +# Similar to "[Cells] exterior cell load distance", controls +# how many cells are rendered on the local map. Values higher than the default +# may result in longer loading times. +local map cell distance = 1 + [GUI] # Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). From 8e5398d85b72e951a3804df4922a47a5a95e6ce2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 14:35:29 +0100 Subject: [PATCH 171/765] Add missing initializations --- apps/openmw/mwgui/mapwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 3b5f0d8aab..362d2d9e00 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -168,6 +168,8 @@ namespace MWGui , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) + , mNumCells(0) + , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) From c9a67ab42357bdb28d63e243946dc734a9030610 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 18:29:33 +0100 Subject: [PATCH 172/765] Do not add scripts from levelled creatures twice (Bug #2806) Do not insert objects from within a CellStore visitor --- apps/openmw/mwworld/cellstore.hpp | 3 ++ apps/openmw/mwworld/scene.cpp | 70 ++++++++++++++++++------------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index faba76262b..2f483f0ba7 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -246,6 +246,7 @@ namespace MWWorld /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Prefer using forEachConst when possible. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template @@ -269,6 +270,7 @@ namespace MWWorld /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template @@ -291,6 +293,7 @@ namespace MWWorld /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index dc93c3b1d7..618a219f31 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -86,10 +86,13 @@ namespace MWPhysics::PhysicsSystem& mPhysics; MWRender::RenderingManager& mRendering; + std::vector mToInsert; + InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering); bool operator() (const MWWorld::Ptr& ptr); + void insert(); }; InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, @@ -102,33 +105,43 @@ namespace bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) { - if (mRescale) - { - if (ptr.getCellRef().getScale()<0.5) - ptr.getCellRef().setScale(0.5); - else if (ptr.getCellRef().getScale()>2) - ptr.getCellRef().setScale(2); - } - - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - { - try - { - addObject(ptr, mPhysics, mRendering); - updateObjectRotation(ptr, mPhysics, mRendering, false); - } - catch (const std::exception& e) - { - std::string error ("error during rendering '" + ptr.getCellRef().getRefId() + "': "); - std::cerr << error + e.what() << std::endl; - } - } - - mLoadingListener.increaseProgress (1); - + // do not insert directly as we can't modify the cell from within the visitation + // CreatureLevList::insertObjectRendering may spawn a new creature + mToInsert.push_back(ptr); return true; } + void InsertVisitor::insert() + { + for (std::vector::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it) + { + MWWorld::Ptr ptr = *it; + if (mRescale) + { + if (ptr.getCellRef().getScale()<0.5) + ptr.getCellRef().setScale(0.5); + else if (ptr.getCellRef().getScale()>2) + ptr.getCellRef().setScale(2); + } + + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + { + try + { + addObject(ptr, mPhysics, mRendering); + updateObjectRotation(ptr, mPhysics, mRendering, false); + } + catch (const std::exception& e) + { + std::string error ("error during rendering '" + ptr.getCellRef().getRefId() + "': "); + std::cerr << error + e.what() << std::endl; + } + } + + mLoadingListener.increaseProgress (1); + } + } + struct AdjustPositionVisitor { bool operator() (const MWWorld::Ptr& ptr) @@ -248,6 +261,10 @@ namespace MWWorld cell->respawn(); + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); @@ -267,10 +284,6 @@ namespace MWWorld if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); } - - // register local scripts - // ??? Should this go into the above if block ??? - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); } void Scene::changeToVoid() @@ -534,6 +547,7 @@ namespace MWWorld { InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering); cell.forEach (insertVisitor); + insertVisitor.insert(); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order AdjustPositionVisitor adjustPosVisitor; From b06730ac612cadfa81c382712fe5d22a69c16c79 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 21:02:02 +0100 Subject: [PATCH 173/765] Fix terrain textureCompileDummy --- components/terrain/terraingrid.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index aa82e0bd65..fb037c003c 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -133,12 +133,12 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu // For compiling textures, I don't think the osgFX::Effect does it correctly osg::ref_ptr textureCompileDummy (new osg::Node); - + unsigned int dummyTextureCounter = 0; std::vector > layerTextures; for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } std::vector > blendmapTextures; @@ -153,7 +153,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setResizeNonPowerOfTwoHint(false); blendmapTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } // use texture coordinates for both texture units, the layer texture and blend texture From 5b972ee7776e198c374f8ff76d3cae1682175397 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 18:51:51 +0100 Subject: [PATCH 174/765] Move texture filtering settings to SceneManager Practical benefits: - Filter settings are now applied to native OSG format models. These models do not use TextureManager::getTexture2D since the model itself specifies a Texture. - The GUI render manager will be able to use its own separate textures, making it easier to turn off filtering for them. --- apps/openmw/engine.cpp | 6 +- apps/openmw/mwrender/animation.cpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/nifosg/controller.hpp | 2 + components/resource/scenemanager.cpp | 207 ++++++++++++++++++++++ components/resource/scenemanager.hpp | 31 ++++ components/resource/texturemanager.cpp | 92 ---------- components/resource/texturemanager.hpp | 17 -- 8 files changed, 247 insertions(+), 114 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f347bc3506..d96bf16e4e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include @@ -448,8 +448,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); - mResourceSystem->getTextureManager()->setUnRefImageDataAfterApply(true); - mResourceSystem->getTextureManager()->setFilterSettings( + mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(true); + mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 549d0eb8e6..a7bb3f1281 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1059,7 +1059,9 @@ namespace MWRender stream << i; stream << ".dds"; - textures.push_back(mResourceSystem->getTextureManager()->getTexture2D(stream.str(), osg::Texture2D::REPEAT, osg::Texture2D::REPEAT)); + osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(stream.str(), osg::Texture2D::REPEAT, osg::Texture2D::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); } osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c8d0925b0b..755a8019b1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -776,7 +776,7 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { - mResourceSystem->getTextureManager()->setFilterSettings( + mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index e1e969d066..0b633a1224 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -305,6 +305,8 @@ namespace NifOsg META_Object(NifOsg, FlipController) + std::vector >& getTextures() { return mTextures; } + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 43ece41f35..80e6f737df 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -9,6 +9,8 @@ #include +#include + #include #include @@ -19,6 +21,7 @@ #include #include +#include #include "texturemanager.hpp" #include "niffilemanager.hpp" @@ -105,10 +108,123 @@ namespace namespace Resource { + /// Set texture filtering settings on textures contained in a FlipController. + class SetFilterSettingsControllerVisitor : public SceneUtil::ControllerVisitor + { + public: + SetFilterSettingsControllerVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + : mMinFilter(minFilter) + , mMagFilter(magFilter) + , mMaxAnisotropy(maxAnisotropy) + { + } + + virtual void visit(osg::Node& node, SceneUtil::Controller& ctrl) + { + if (NifOsg::FlipController* flipctrl = dynamic_cast(&ctrl)) + { + for (std::vector >::iterator it = flipctrl->getTextures().begin(); it != flipctrl->getTextures().end(); ++it) + { + osg::Texture* tex = *it; + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + mTexturesProcessed.insert(tex); + } + } + } + + const std::set >& getTexturesProcessed() + { + return mTexturesProcessed; + } + + private: + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + std::set > mTexturesProcessed; + }; + + /// Set texture filtering settings on textures contained in StateSets. + class SetFilterSettingsVisitor : public osg::NodeVisitor + { + public: + SetFilterSettingsVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMinFilter(minFilter) + , mMagFilter(magFilter) + , mMaxAnisotropy(maxAnisotropy) + { + } + + virtual void apply(osg::Node& node) + { + osg::StateSet* stateset = node.getStateSet(); + if (stateset) + apply(stateset); + traverse(node); + } + + virtual void apply(osg::Geode& geode) + { + osg::StateSet* stateset = geode.getStateSet(); + if (stateset) + apply(stateset); + + for (unsigned int i=0; igetStateSet(); + if (stateset) + apply(stateset); + } + } + + void apply(osg::StateSet* stateset) + { + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + apply(texture); + } + } + + void apply(osg::StateAttribute* attr) + { + osg::Texture* tex = attr->asTexture(); + if (tex) + { + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + mTexturesProcessed.insert(tex); + } + } + + const std::set >& getTexturesProcessed() + { + return mTexturesProcessed; + } + + private: + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + std::set > mTexturesProcessed; + }; + + + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager) : mVFS(vfs) , mTextureManager(textureManager) , mNifFileManager(nifFileManager) + , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) + , mMagFilter(osg::Texture::LINEAR) + , mMaxAnisotropy(1) + , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) { } @@ -219,6 +335,17 @@ namespace Resource throw; } + // set filtering settings + SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); + loaded->accept(setFilterSettingsVisitor); + SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); + loaded->accept(setFilterSettingsControllerVisitor); + + // remember which textures we set a filtering setting on so we can re-apply it when the setting changes + mTexturesWithFilterSetting.insert(setFilterSettingsVisitor.getTexturesProcessed().begin(), setFilterSettingsVisitor.getTexturesProcessed().end()); + mTexturesWithFilterSetting.insert(setFilterSettingsControllerVisitor.getTexturesProcessed().begin(), setFilterSettingsControllerVisitor.getTexturesProcessed().end()); + + // share state osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); if (mIncrementalCompileOperation) @@ -285,4 +412,84 @@ namespace Resource mParticleSystemMask = mask; } + void SceneManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *viewer) + { + osg::Texture::FilterMode min = osg::Texture::LINEAR; + osg::Texture::FilterMode mag = osg::Texture::LINEAR; + + if(magfilter == "nearest") + mag = osg::Texture::NEAREST; + else if(magfilter != "linear") + std::cerr<< "Invalid texture mag filter: "<stopThreading(); + setFilterSettings(min, mag, maxAnisotropy); + if(viewer) viewer->startThreading(); + } + + void SceneManager::applyFilterSettings(osg::Texture *tex) + { + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(mMaxAnisotropy); + mTexturesWithFilterSetting.insert(tex); + } + + void SceneManager::setUnRefImageDataAfterApply(bool unref) + { + mUnRefImageDataAfterApply = unref; + } + + void SceneManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + { + mMinFilter = minFilter; + mMagFilter = magFilter; + mMaxAnisotropy = std::max(1, maxAnisotropy); + + for (std::set >::const_iterator it = mTexturesWithFilterSetting.begin(); it != mTexturesWithFilterSetting.end(); ++it) + { + (*it)->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + (*it)->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + (*it)->setMaxAnisotropy(mMaxAnisotropy); + } + } + + void SceneManager::clearCache() + { + // clear textures that are no longer referenced + for (std::set >::const_iterator it = mTexturesWithFilterSetting.begin(); it != mTexturesWithFilterSetting.end();) + { + osg::Texture* tex = *it; + if (tex->referenceCount() <= 1) + mTexturesWithFilterSetting.erase(it++); + else + ++it; + } + + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 67fa2ab433..df330af5b5 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace Resource { @@ -23,6 +24,11 @@ namespace osgUtil class IncrementalCompileOperation; } +namespace osgViewer +{ + class Viewer; +} + namespace Resource { @@ -69,11 +75,30 @@ namespace Resource /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); + void setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *viewer); + + /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate / createInstance) + /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. + void applyFilterSettings (osg::Texture* tex); + + /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, + /// otherwise should be disabled to reduce memory usage. + void setUnRefImageDataAfterApply(bool unref); + + void clearCache(); + private: const VFS::Manager* mVFS; Resource::TextureManager* mTextureManager; Resource::NifFileManager* mNifFileManager; + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + bool mUnRefImageDataAfterApply; + osg::ref_ptr mIncrementalCompileOperation; unsigned int mParticleSystemMask; @@ -82,8 +107,14 @@ namespace Resource typedef std::map > Index; Index mIndex; + std::set > mTexturesWithFilterSetting; + SceneManager(const SceneManager&); void operator = (const SceneManager&); + + /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! + void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); + }; } diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp index d7f3fc61a1..b49c6bb094 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/texturemanager.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include @@ -47,11 +46,7 @@ namespace Resource TextureManager::TextureManager(const VFS::Manager *vfs) : mVFS(vfs) - , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) - , mMagFilter(osg::Texture::LINEAR) - , mMaxAnisotropy(1) , mWarningTexture(createWarningTexture()) - , mUnRefImageDataAfterApply(false) { } @@ -61,88 +56,6 @@ namespace Resource } - void TextureManager::setUnRefImageDataAfterApply(bool unref) - { - mUnRefImageDataAfterApply = unref; - } - - void TextureManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer) - { - osg::Texture::FilterMode min = osg::Texture::LINEAR; - osg::Texture::FilterMode mag = osg::Texture::LINEAR; - - if(magfilter == "nearest") - mag = osg::Texture::NEAREST; - else if(magfilter != "linear") - std::cerr<< "Invalid texture mag filter: "<stopThreading(); - setFilterSettings(min, mag, maxAnisotropy); - if(viewer) viewer->startThreading(); - } - - void TextureManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) - { - mMinFilter = minFilter; - mMagFilter = magFilter; - mMaxAnisotropy = std::max(1, maxAnisotropy); - - for (std::map >::iterator it = mTextures.begin(); it != mTextures.end(); ++it) - { - osg::ref_ptr tex = it->second; - - // Keep mip-mapping disabled if the texture creator explicitely requested no mipmapping. - osg::Texture::FilterMode oldMin = tex->getFilter(osg::Texture::MIN_FILTER); - if (oldMin == osg::Texture::LINEAR || oldMin == osg::Texture::NEAREST) - { - osg::Texture::FilterMode newMin = osg::Texture::LINEAR; - switch (mMinFilter) - { - case osg::Texture::LINEAR: - case osg::Texture::LINEAR_MIPMAP_LINEAR: - case osg::Texture::LINEAR_MIPMAP_NEAREST: - newMin = osg::Texture::LINEAR; - break; - case osg::Texture::NEAREST: - case osg::Texture::NEAREST_MIPMAP_LINEAR: - case osg::Texture::NEAREST_MIPMAP_NEAREST: - newMin = osg::Texture::NEAREST; - break; - } - tex->setFilter(osg::Texture::MIN_FILTER, newMin); - } - else - tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); - - tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); - tex->setMaxAnisotropy(static_cast(mMaxAnisotropy)); - } - } - /* osg::ref_ptr TextureManager::getImage(const std::string &filename) { @@ -295,11 +208,6 @@ namespace Resource texture->setImage(image); texture->setWrap(osg::Texture::WRAP_S, wrapS); texture->setWrap(osg::Texture::WRAP_T, wrapT); - texture->setFilter(osg::Texture::MIN_FILTER, mMinFilter); - texture->setFilter(osg::Texture::MAG_FILTER, mMagFilter); - texture->setMaxAnisotropy(mMaxAnisotropy); - - texture->setUnRefImageDataAfterApply(mUnRefImageDataAfterApply); mTextures.insert(std::make_pair(key, texture)); return texture; diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp index e12dfa0900..61a727b5ee 100644 --- a/components/resource/texturemanager.hpp +++ b/components/resource/texturemanager.hpp @@ -28,14 +28,6 @@ namespace Resource TextureManager(const VFS::Manager* vfs); ~TextureManager(); - void setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *view); - - /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, - /// otherwise should be disabled to reduce memory usage. - void setUnRefImageDataAfterApply(bool unref); - /// Create or retrieve a Texture2D using the specified image filename, and wrap parameters. /// Returns the dummy texture if the given texture is not found. osg::ref_ptr getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT); @@ -50,10 +42,6 @@ namespace Resource private: const VFS::Manager* mVFS; - osg::Texture::FilterMode mMinFilter; - osg::Texture::FilterMode mMagFilter; - int mMaxAnisotropy; - typedef std::pair, std::string> MapKey; std::map > mImages; @@ -62,11 +50,6 @@ namespace Resource osg::ref_ptr mWarningTexture; - bool mUnRefImageDataAfterApply; - - /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! - void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); - TextureManager(const TextureManager&); void operator = (const TextureManager&); }; From e2ee1d5689e1803cd7f63b94dbcfb7ff70b07f13 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 18:58:22 +0100 Subject: [PATCH 175/765] Use separate textures for the MyGUI RenderManager This means we can more reliably set the filter parameters. I believe this commit creates a regression where non-DDS GUI textures would display upside down, which will be addressed by further refactoring in the next commits. --- components/myguiplatform/myguitexture.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 50ac5c1f34..a42568cf85 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -86,7 +86,9 @@ namespace osgMyGUI if (!mTextureManager) throw std::runtime_error("No texturemanager set"); - mTexture = mTextureManager->getTexture2D(fname, osg::Texture2D::CLAMP_TO_EDGE, osg::Texture2D::CLAMP_TO_EDGE); + mTexture = new osg::Texture2D(mTextureManager->getImage(fname)); + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); // disable mip-maps mTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); From 6ac688c0e2e16e4eec74ed5ef2d6a1a05a367d66 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 19:28:52 +0100 Subject: [PATCH 176/765] Change the way that texture filtering setting changes are applied at runtime to not require keeping a reference to textures The references would be difficult to clean up because there may or may not be another reference to the texture in the osgDB::SharedStateManager. --- components/resource/scenemanager.cpp | 68 ++++++++-------------------- components/resource/scenemanager.hpp | 6 +-- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 80e6f737df..df103f50c9 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -129,21 +129,14 @@ namespace Resource tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); - mTexturesProcessed.insert(tex); } } } - const std::set >& getTexturesProcessed() - { - return mTexturesProcessed; - } - private: osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; int mMaxAnisotropy; - std::set > mTexturesProcessed; }; /// Set texture filtering settings on textures contained in StateSets. @@ -199,20 +192,12 @@ namespace Resource tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); - mTexturesProcessed.insert(tex); } } - - const std::set >& getTexturesProcessed() - { - return mTexturesProcessed; - } - private: osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; int mMaxAnisotropy; - std::set > mTexturesProcessed; }; @@ -341,10 +326,6 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - // remember which textures we set a filtering setting on so we can re-apply it when the setting changes - mTexturesWithFilterSetting.insert(setFilterSettingsVisitor.getTexturesProcessed().begin(), setFilterSettingsVisitor.getTexturesProcessed().end()); - mTexturesWithFilterSetting.insert(setFilterSettingsControllerVisitor.getTexturesProcessed().begin(), setFilterSettingsControllerVisitor.getTexturesProcessed().end()); - // share state osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); @@ -447,7 +428,26 @@ namespace Resource } if(viewer) viewer->stopThreading(); - setFilterSettings(min, mag, maxAnisotropy); + + mMinFilter = min; + mMagFilter = mag; + mMaxAnisotropy = std::max(1, maxAnisotropy); + + SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); + SetFilterSettingsVisitor setFilterSettingsVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); + if (viewer && viewer->getSceneData()) + { + viewer->getSceneData()->accept(setFilterSettingsControllerVisitor); + viewer->getSceneData()->accept(setFilterSettingsVisitor); + } + + for (Index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) + { + osg::Node* node = it->second; + node->accept(setFilterSettingsControllerVisitor); + node->accept(setFilterSettingsVisitor); + } + if(viewer) viewer->startThreading(); } @@ -456,7 +456,6 @@ namespace Resource tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); - mTexturesWithFilterSetting.insert(tex); } void SceneManager::setUnRefImageDataAfterApply(bool unref) @@ -464,32 +463,5 @@ namespace Resource mUnRefImageDataAfterApply = unref; } - void SceneManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) - { - mMinFilter = minFilter; - mMagFilter = magFilter; - mMaxAnisotropy = std::max(1, maxAnisotropy); - - for (std::set >::const_iterator it = mTexturesWithFilterSetting.begin(); it != mTexturesWithFilterSetting.end(); ++it) - { - (*it)->setFilter(osg::Texture::MIN_FILTER, mMinFilter); - (*it)->setFilter(osg::Texture::MAG_FILTER, mMagFilter); - (*it)->setMaxAnisotropy(mMaxAnisotropy); - } - } - - void SceneManager::clearCache() - { - // clear textures that are no longer referenced - for (std::set >::const_iterator it = mTexturesWithFilterSetting.begin(); it != mTexturesWithFilterSetting.end();) - { - osg::Texture* tex = *it; - if (tex->referenceCount() <= 1) - mTexturesWithFilterSetting.erase(it++); - else - ++it; - } - - } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index df330af5b5..ae5fa2db6e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -87,8 +87,6 @@ namespace Resource /// otherwise should be disabled to reduce memory usage. void setUnRefImageDataAfterApply(bool unref); - void clearCache(); - private: const VFS::Manager* mVFS; Resource::TextureManager* mTextureManager; @@ -104,11 +102,9 @@ namespace Resource unsigned int mParticleSystemMask; // observer_ptr? - typedef std::map > Index; + typedef std::map > Index; Index mIndex; - std::set > mTexturesWithFilterSetting; - SceneManager(const SceneManager&); void operator = (const SceneManager&); From e8662bea3133ba9dbb09b86c3abb1af39425e90d Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 20:23:41 +0100 Subject: [PATCH 177/765] Change the way that image origin is converted to OpenGL's lower-left convention Flip the texture coordinates instead of flipping textures. This simplifies the TextureManager (no need to worry if the caller wants flipping or not), should make it easier to generalize & multithread it. --- apps/openmw/mwgui/inventorywindow.cpp | 6 +++--- apps/openmw/mwgui/loadingscreen.cpp | 2 +- apps/openmw/mwgui/mapwindow.cpp | 8 ++++---- apps/openmw/mwgui/race.cpp | 2 +- apps/openmw/mwgui/savegamedialog.cpp | 2 +- apps/openmw/mwgui/videowidget.cpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 2 +- .../myguiplatform/myguirendermanager.cpp | 7 +++++++ components/myguiplatform/myguitexture.cpp | 2 ++ components/nif/data.cpp | 10 ++++++++-- components/resource/texturemanager.cpp | 18 +++--------------- components/resource/texturemanager.hpp | 6 ++++++ components/sdlutil/sdlcursormanager.cpp | 2 +- files/mygui/openmw_pointer.xml | 4 ++-- 14 files changed, 41 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index facb17d66c..4599132e3d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -87,7 +87,7 @@ namespace MWGui mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); mAvatarImage->setRenderItemTexture(mPreviewTexture.get()); - mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); @@ -403,8 +403,8 @@ namespace MWGui int height = std::min(mPreview->getTextureHeight(), size.height); mPreview->setViewport(width, height); - mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, height/float(mPreview->getTextureHeight()), - width/float(mPreview->getTextureWidth()), 0.f)); + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, + width/float(mPreview->getTextureWidth()), height/float(mPreview->getTextureHeight()))); } void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index e32140cb35..6385eb2c6e 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -164,7 +164,7 @@ namespace MWGui mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setRenderItemTexture(mGuiTexture.get()); - mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } setVisible(true); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 362d2d9e00..1b8d5ab43e 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -254,7 +254,7 @@ namespace MWGui { boost::shared_ptr myguitex (new osgMyGUI::OSGTexture(tex)); fog->setRenderItemTexture(myguitex.get()); - fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); fogTextures.push_back(myguitex); } else @@ -391,7 +391,7 @@ namespace MWGui boost::shared_ptr guiTex (new osgMyGUI::OSGTexture(texture)); textures.push_back(guiTex); box->setRenderItemTexture(guiTex.get()); - box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } else box->setRenderItemTexture(NULL); @@ -770,11 +770,11 @@ namespace MWGui mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); - mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); - mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } MapWindow::~MapWindow() diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index a65379fca4..aa229de72d 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -142,7 +142,7 @@ namespace MWGui mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); - mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); const ESM::NPC& proto = mPreview->getPrototype(); setRaceId(proto.mRace); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 53c2807374..9de2374add 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -422,6 +422,6 @@ namespace MWGui mScreenshotTexture.reset(new osgMyGUI::OSGTexture(texture)); mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); - mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } } diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index d28ea0b660..5a4bb981f4 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -50,7 +50,7 @@ void VideoWidget::playVideo(const std::string &video) mTexture.reset(new osgMyGUI::OSGTexture(texture)); setRenderItemTexture(mTexture.get()); - getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } int VideoWidget::getVideoWidth() diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index a6f68b5d4c..2588540540 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -194,7 +194,7 @@ namespace MWRender sizeX = std::max(sizeX, 0); sizeY = std::max(sizeY, 0); - mCamera->setViewport(0, 0, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); + mCamera->setViewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); redraw(); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 5bd56dc8f4..f5bebd560f 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -189,6 +190,12 @@ public: mStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); mStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mStateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + + // need to flip tex coords since MyGUI uses DirectX convention of top left image origin + osg::Matrix flipMat; + flipMat.preMultTranslate(osg::Vec3f(0,1,0)); + flipMat.preMultScale(osg::Vec3f(1,-1,1)); + mStateSet->setTextureAttribute(0, new osg::TexMat(flipMat), osg::StateAttribute::ON); } Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) : osg::Drawable(copy, copyop) diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index a42568cf85..3560a3ae3b 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -145,6 +145,8 @@ namespace osgMyGUI if (!mLockedImage.valid()) throw std::runtime_error("Texture not locked"); + mLockedImage->flipVertical(); + // mTexture might be in use by the draw thread, so create a new texture instead and use that. osg::ref_ptr newTexture = new osg::Texture2D; newTexture->setTextureSize(getWidth(), getHeight()); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 5a60ab8a5e..08c268dde1 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -65,8 +65,14 @@ void ShapeData::read(NIFStream *nif) uvlist.resize(uvs); for(int i = 0;i < uvs;i++) { - uvlist[i] = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); - nif->getVector2s(uvlist[i], verts); + osg::Vec2Array* list = uvlist[i] = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); + nif->getVector2s(list, verts); + + // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin + for (unsigned int uv=0; uvsize(); ++uv) + { + (*list)[uv] = osg::Vec2((*list)[uv].x(), 1.f - (*list)[uv].y()); + } } } } diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp index b49c6bb094..709b925f12 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/texturemanager.cpp @@ -47,8 +47,8 @@ namespace Resource TextureManager::TextureManager(const VFS::Manager *vfs) : mVFS(vfs) , mWarningTexture(createWarningTexture()) + , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) { - } TextureManager::~TextureManager() @@ -116,8 +116,6 @@ namespace Resource return NULL; } - osg::ref_ptr opts (new osgDB::Options); - opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option size_t extPos = normalized.find_last_of('.'); std::string ext; if (extPos != std::string::npos && extPos+1 < normalized.size()) @@ -129,7 +127,7 @@ namespace Resource return NULL; } - osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); if (!result.success()) { std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; @@ -170,8 +168,6 @@ namespace Resource return mWarningTexture; } - osg::ref_ptr opts (new osgDB::Options); - opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option size_t extPos = normalized.find_last_of('.'); std::string ext; if (extPos != std::string::npos && extPos+1 < normalized.size()) @@ -183,7 +179,7 @@ namespace Resource return mWarningTexture; } - osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); if (!result.success()) { std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; @@ -196,14 +192,6 @@ namespace Resource return mWarningTexture; } - // We need to flip images, because the Morrowind texture coordinates use the DirectX convention (top-left image origin), - // but OpenGL uses bottom left as the image origin. - // For some reason this doesn't concern DDS textures, which are already flipped when loaded. - if (ext != "dds") - { - image->flipVertical(); - } - osg::ref_ptr texture(new osg::Texture2D); texture->setImage(image); texture->setWrap(osg::Texture::WRAP_S, wrapS); diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp index 61a727b5ee..cb2711aa73 100644 --- a/components/resource/texturemanager.hpp +++ b/components/resource/texturemanager.hpp @@ -18,6 +18,11 @@ namespace VFS class Manager; } +namespace osgDB +{ + class Options; +} + namespace Resource { @@ -49,6 +54,7 @@ namespace Resource std::map > mTextures; osg::ref_ptr mWarningTexture; + osg::ref_ptr mOptions; TextureManager(const TextureManager&); void operator = (const TextureManager&); diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 9ecef04839..afe2406096 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -231,7 +231,7 @@ namespace SDLUtil return; } - SDL_Surface* surf = SDLUtil::imageToSurface(decompressed, false); + SDL_Surface* surf = SDLUtil::imageToSurface(decompressed, true); //set the cursor and store it for later SDL_Cursor* curs = SDL_CreateColorCursor(surf, hotspot_x, hotspot_y); diff --git a/files/mygui/openmw_pointer.xml b/files/mygui/openmw_pointer.xml index a55a5453c0..4a2f818600 100644 --- a/files/mygui/openmw_pointer.xml +++ b/files/mygui/openmw_pointer.xml @@ -23,13 +23,13 @@ - +
    - + From 31988ca4cc643e9b1e76597f8574a30216513cc9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 21:07:08 +0100 Subject: [PATCH 178/765] Add a dont_override_filter description for textures that should be left alone --- components/resource/scenemanager.cpp | 7 +++++++ components/terrain/terraingrid.cpp | 1 + 2 files changed, 8 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index df103f50c9..065187c79e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -189,6 +189,13 @@ namespace Resource osg::Texture* tex = attr->asTexture(); if (tex) { + if (tex->getUserDataContainer()) + { + const std::vector& descriptions = tex->getUserDataContainer()->getDescriptions(); + if (std::find(descriptions.begin(), descriptions.end(), "dont_override_filter") != descriptions.end()) + return; + } + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index fb037c003c..b7d7277301 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -151,6 +151,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setResizeNonPowerOfTwoHint(false); + texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); From 71401aafe7eaef6b864fb0b1e6ad86f89e9a70c3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 21:08:32 +0100 Subject: [PATCH 179/765] Handle multipass techniques in SetFilterSettingsVisitor --- components/resource/scenemanager.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 065187c79e..e3f38e8867 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -153,12 +154,29 @@ namespace Resource virtual void apply(osg::Node& node) { + if (osgFX::Effect* effect = dynamic_cast(&node)) + apply(*effect); + osg::StateSet* stateset = node.getStateSet(); if (stateset) apply(stateset); + traverse(node); } + void apply(osgFX::Effect& effect) + { + for (int i =0; igetNumPasses(); ++pass) + { + if (tech->getPassStateSet(pass)) + apply(tech->getPassStateSet(pass)); + } + } + } + virtual void apply(osg::Geode& geode) { osg::StateSet* stateset = geode.getStateSet(); From fbd4ad9b0cac68457a33834ee7482c08d95e3c01 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 22:46:15 +0100 Subject: [PATCH 180/765] Flip terrain textures --- components/esmterrain/storage.cpp | 4 ++-- components/terrain/buffercache.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 4be56d5e6a..267b831ec5 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -399,9 +399,9 @@ namespace ESMTerrain int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; if (blendIndex == i) - pData[y*blendmapSize*channels + x*channels + channel] = 255; + pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255; else - pData[y*blendmapSize*channels + x*channels + channel] = 0; + pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; } } diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index a64f8ffd1d..f9c860d47b 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -193,7 +193,7 @@ namespace Terrain for (unsigned int row = 0; row < mNumVerts; ++row) { uvs->push_back(osg::Vec2f(col / static_cast(mNumVerts-1), - row / static_cast(mNumVerts-1))); + ((mNumVerts-1) - row) / static_cast(mNumVerts-1))); } } From 6ef848b7c50497438dd0cf85a17709a7bb0dfb75 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 22:58:02 +0100 Subject: [PATCH 181/765] Remove TextureManager::getTexture2D Instead use getImage and let the caller create the Texture. Sharing of textures is then handled in post by the SharedStateManager. This is closer to what the OSG serializer does. Streamlines the TextureManager and will make it easier to multithread. --- apps/openmw/mwgui/windowmanagerimp.cpp | 6 +- apps/openmw/mwrender/animation.cpp | 5 +- apps/openmw/mwrender/renderingmanager.cpp | 7 +++ apps/openmw/mwrender/renderingmanager.hpp | 2 + apps/openmw/mwrender/ripplesimulation.cpp | 7 ++- apps/openmw/mwrender/sky.cpp | 42 ++++++++----- apps/openmw/mwrender/util.cpp | 6 +- apps/openmw/mwrender/water.cpp | 8 ++- apps/openmw/mwworld/scene.cpp | 4 +- components/nifosg/nifloader.cpp | 11 ++-- components/resource/texturemanager.cpp | 75 ++--------------------- components/resource/texturemanager.hpp | 13 ++-- components/terrain/terraingrid.cpp | 30 ++++++++- components/terrain/terraingrid.hpp | 6 ++ components/terrain/world.hpp | 2 + 15 files changed, 114 insertions(+), 110 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 90c0494f6d..346c672f79 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2015,9 +2015,9 @@ namespace MWGui continue; std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; - osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP); + osg::ref_ptr image = mResourceSystem->getTextureManager()->getImage(tex_name); - if(tex.valid()) + if(image.valid()) { //everything looks good, send it to the cursor manager Uint8 size_x = imgSetPointer->getSize().width; @@ -2026,7 +2026,7 @@ namespace MWGui Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); - mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y); + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, size_x, size_y, hotspot_x, hotspot_y); } } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a7bb3f1281..35602fa172 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1059,7 +1059,10 @@ namespace MWRender stream << i; stream << ".dds"; - osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(stream.str(), osg::Texture2D::REPEAT, osg::Texture2D::REPEAT); + osg::ref_ptr image = mResourceSystem->getTextureManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 755a8019b1..2237d81252 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -231,6 +231,13 @@ namespace MWRender return mResourceSystem; } + void RenderingManager::clearCache() + { + mResourceSystem->clearCache(); + if (mTerrain.get()) + mTerrain->clearCache(); + } + osg::Group* RenderingManager::getLightRoot() { return mLightRoot.get(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 3b583af89c..550e48e636 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -65,6 +65,8 @@ namespace MWRender Resource::ResourceSystem* getResourceSystem(); + void clearCache(); + osg::Group* getLightRoot(); void setNightEyeFactor(float factor); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f74631c4a2..603cd397b1 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "vismask.hpp" @@ -39,7 +40,11 @@ namespace { std::ostringstream texname; texname << "textures/water/" << tex << std::setw(2) << std::setfill('0') << i << ".dds"; - textures.push_back(resourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr tex (new osg::Texture2D(resourceSystem->getTextureManager()->getImage(texname.str()))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + resourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); } osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 513fc74a34..2a25baea90 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -476,9 +476,9 @@ public: mTransform->addUpdateCallback(mUpdater); mTransform->setNodeMask(Mask_Sun); - osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", - osg::Texture::CLAMP, - osg::Texture::CLAMP); + osg::ref_ptr sunTex (new osg::Texture2D(textureManager.getImage("textures/tx_sun_05.dds"))); + sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mGeode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); @@ -604,9 +604,9 @@ private: void createSunFlash(Resource::TextureManager& textureManager) { - osg::ref_ptr tex = textureManager.getTexture2D("textures/tx_sun_flash_grey_05.dds", - osg::Texture::CLAMP, - osg::Texture::CLAMP); + osg::ref_ptr tex (new osg::Texture2D(textureManager.getImage("textures/tx_sun_flash_grey_05.dds"))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); osg::ref_ptr transform (new osg::PositionAttitudeTransform); const float scale = 2.6f; @@ -1055,8 +1055,12 @@ private: void setTextures(const std::string& phaseTex, const std::string& circleTex) { - mPhaseTex = mTextureManager.getTexture2D(phaseTex, osg::Texture::CLAMP, osg::Texture::CLAMP); - mCircleTex = mTextureManager.getTexture2D(circleTex, osg::Texture::CLAMP, osg::Texture::CLAMP); + mPhaseTex = new osg::Texture2D(mTextureManager.getImage(phaseTex)); + mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mCircleTex = new osg::Texture2D(mTextureManager.getImage(circleTex)); + mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); reset(); } @@ -1342,8 +1346,12 @@ void SkyManager::createRain() mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,-1)); osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - stateset->setTextureAttributeAndModes(0, mSceneManager->getTextureManager()->getTexture2D("textures/tx_raindrop_01.dds", - osg::Texture::CLAMP, osg::Texture::CLAMP), osg::StateAttribute::ON); + + osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage("textures/tx_raindrop_01.dds"))); + raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -1549,8 +1557,11 @@ void SkyManager::setWeather(const WeatherResult& weather) std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); - mCloudUpdater->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, - osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage(texture))); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mCloudUpdater->setTexture(cloudTex); } if (mNextClouds != weather.mNextCloudTexture) @@ -1561,8 +1572,11 @@ void SkyManager::setWeather(const WeatherResult& weather) { std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); - mCloudUpdater2->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, - osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage(texture))); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mCloudUpdater2->setTexture(cloudTex); } } diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index e1af1c3393..df6a0497f1 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -15,8 +15,10 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou return; std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); // Not sure if wrap settings should be pulled from the overridden texture? - osg::ref_ptr tex = resourceSystem->getTextureManager()->getTexture2D(correctedTexture, osg::Texture2D::CLAMP, - osg::Texture2D::CLAMP); + osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getTextureManager()->getImage(correctedTexture)); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + osg::ref_ptr stateset; if (node->getStateSet()) stateset = static_cast(node->getStateSet()->clone(osg::CopyOp::SHALLOW_COPY)); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c7f2c9cc6e..ff31564119 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -317,6 +317,7 @@ public: mRefractionTexture->setInternalFormat(GL_RGB); mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mRefractionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); @@ -328,6 +329,7 @@ public: mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mRefractionDepthTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } @@ -388,6 +390,7 @@ public: mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mReflectionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); @@ -553,7 +556,10 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { std::ostringstream texname; texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; - textures.push_back(mResourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr tex (new osg::Texture2D(mResourceSystem->getTextureManager()->getImage(texname.str()))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + textures.push_back(tex); } if (!textures.size()) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 618a219f31..f30e6efe10 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -402,7 +402,7 @@ namespace MWWorld mCellChanged = true; - mRendering.getResourceSystem()->clearCache(); + mRendering.clearCache(); } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -515,7 +515,7 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); - mRendering.getResourceSystem()->clearCache(); + mRendering.clearCache(); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 79f8379532..190fd020fe 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -709,7 +709,9 @@ namespace NifOsg } std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); - osg::ref_ptr texture = textureManager->getTexture2D(filename, wrapS, wrapT); + osg::ref_ptr texture (new osg::Texture2D(textureManager->getImage(filename))); + texture->setWrap(osg::Texture::WRAP_S, wrapS); + texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -1376,9 +1378,10 @@ namespace NifOsg int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; - osg::ref_ptr texture2d = textureManager->getTexture2D(filename, - wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP, - wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + // create a new texture, will later attempt to share using the SharedStateManager + osg::ref_ptr texture2d (new osg::Texture2D(textureManager->getImage(filename))); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); int texUnit = boundTextures.size(); diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp index 709b925f12..b3f23ab406 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/texturemanager.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #ifdef OSG_LIBRARY_STATIC @@ -47,6 +45,7 @@ namespace Resource TextureManager::TextureManager(const VFS::Manager *vfs) : mVFS(vfs) , mWarningTexture(createWarningTexture()) + , mWarningImage(mWarningTexture->getImage()) , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) { } @@ -56,13 +55,6 @@ namespace Resource } - /* - osg::ref_ptr TextureManager::getImage(const std::string &filename) - { - - } - */ - bool checkSupported(osg::Image* image, const std::string& filename) { switch(image->getPixelFormat()) @@ -113,7 +105,7 @@ namespace Resource catch (std::exception& e) { std::cerr << "Failed to open image: " << e.what() << std::endl; - return NULL; + return mWarningImage; } size_t extPos = normalized.find_last_of('.'); @@ -124,20 +116,20 @@ namespace Resource if (!reader) { std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; - return NULL; + return mWarningImage; } osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); if (!result.success()) { std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; - return NULL; + return mWarningImage; } osg::Image* image = result.getImage(); if (!checkSupported(image, filename)) { - return NULL; + return mWarningImage; } mImages.insert(std::make_pair(normalized, image)); @@ -145,63 +137,6 @@ namespace Resource } } - osg::ref_ptr TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT) - { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); - MapKey key = std::make_pair(std::make_pair(wrapS, wrapT), normalized); - std::map >::iterator found = mTextures.find(key); - if (found != mTextures.end()) - { - return found->second; - } - else - { - Files::IStreamPtr stream; - try - { - stream = mVFS->get(normalized.c_str()); - } - catch (std::exception& e) - { - std::cerr << "Failed to open texture: " << e.what() << std::endl; - return mWarningTexture; - } - - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); - if (!reader) - { - std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; - return mWarningTexture; - } - - osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); - if (!result.success()) - { - std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; - return mWarningTexture; - } - - osg::Image* image = result.getImage(); - if (!checkSupported(image, filename)) - { - return mWarningTexture; - } - - osg::ref_ptr texture(new osg::Texture2D); - texture->setImage(image); - texture->setWrap(osg::Texture::WRAP_S, wrapS); - texture->setWrap(osg::Texture::WRAP_T, wrapT); - - mTextures.insert(std::make_pair(key, texture)); - return texture; - } - } - osg::Texture2D* TextureManager::getWarningTexture() { return mWarningTexture.get(); diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp index cb2711aa73..0290d2340b 100644 --- a/components/resource/texturemanager.hpp +++ b/components/resource/texturemanager.hpp @@ -26,18 +26,15 @@ namespace osgDB namespace Resource { - /// @brief Handles loading/caching of Images and Texture StateAttributes. + /// @brief Handles loading/caching of Images. class TextureManager { public: TextureManager(const VFS::Manager* vfs); ~TextureManager(); - /// Create or retrieve a Texture2D using the specified image filename, and wrap parameters. - /// Returns the dummy texture if the given texture is not found. - osg::ref_ptr getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT); - /// Create or retrieve an Image + /// Returns the dummy image if the given image is not found. osg::ref_ptr getImage(const std::string& filename); const VFS::Manager* getVFS() { return mVFS; } @@ -47,13 +44,11 @@ namespace Resource private: const VFS::Manager* mVFS; - typedef std::pair, std::string> MapKey; - + // TODO: use ObjectCache std::map > mImages; - std::map > mTextures; - osg::ref_ptr mWarningTexture; + osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; TextureManager(const TextureManager&); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index b7d7277301..58675a7140 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -134,10 +135,19 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu // For compiling textures, I don't think the osgFX::Effect does it correctly osg::ref_ptr textureCompileDummy (new osg::Node); unsigned int dummyTextureCounter = 0; + std::vector > layerTextures; for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); + osg::ref_ptr tex = mTextureCache[it->mDiffuseMap]; + if (!tex) + { + tex = new osg::Texture2D(mResourceSystem->getTextureManager()->getImage(it->mDiffuseMap)); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mTextureCache[it->mDiffuseMap] = tex; + } + layerTextures.push_back(tex); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } @@ -148,15 +158,18 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setImage(*it); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setResizeNonPowerOfTwoHint(false); texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } + // SharedStatemanager->share(textureCompileDummy); + + // Remove unrefImageDataAfterApply for better state sharing + // use texture coordinates for both texture units, the layer texture and blend texture for (unsigned int i=0; i<2; ++i) geometry->setTexCoordArray(i, mCache.getUVBuffer()); @@ -226,4 +239,15 @@ void TerrainGrid::unloadCell(int x, int y) mGrid.erase(it); } +void TerrainGrid::clearCache() +{ + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) + { + if (it->second->referenceCount() <= 1) + mTextureCache.erase(it++); + else + ++it; + } +} + } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 169a9a6228..81bfc4211e 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -27,12 +27,18 @@ namespace Terrain virtual void loadCell(int x, int y); virtual void unloadCell(int x, int y); + /// Clear cached textures that are no longer referenced + void clearCache(); + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; + typedef std::map > TextureCache; + TextureCache mTextureCache; + typedef std::map, GridElement*> Grid; Grid mGrid; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 4212f2a0c2..bd5d0a4661 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,6 +39,8 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); + virtual void clearCache() {} + float getHeightAt (const osg::Vec3f& worldPos); // This is only a hint and may be ignored by the implementation. From f99f403dda1160ed25172571b1dc22aa48f6b8f3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:03:53 +0100 Subject: [PATCH 182/765] Rename TextureManager to ImageManager --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 14 +++++++------- apps/openmw/mwrender/util.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- components/CMakeLists.txt | 2 +- components/myguiplatform/myguiplatform.cpp | 2 +- components/myguiplatform/myguiplatform.hpp | 4 ++-- components/myguiplatform/myguirendermanager.cpp | 4 ++-- components/myguiplatform/myguirendermanager.hpp | 6 +++--- components/myguiplatform/myguitexture.cpp | 4 ++-- components/myguiplatform/myguitexture.hpp | 6 +++--- components/nifosg/nifloader.cpp | 14 +++++++------- components/nifosg/nifloader.hpp | 4 ++-- .../{texturemanager.cpp => imagemanager.cpp} | 10 +++++----- .../{texturemanager.hpp => imagemanager.hpp} | 10 +++++----- components/resource/resourcesystem.cpp | 6 +++--- components/resource/resourcesystem.hpp | 6 +++--- components/resource/scenemanager.cpp | 12 ++++++------ components/resource/scenemanager.hpp | 8 ++++---- components/terrain/terraingrid.cpp | 2 +- 23 files changed, 63 insertions(+), 63 deletions(-) rename components/resource/{texturemanager.cpp => imagemanager.cpp} (94%) rename components/resource/{texturemanager.hpp => imagemanager.hpp} (83%) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 346c672f79..d053d8bfde 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 35602fa172..2c5831dae1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include // KeyframeHolder #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2237d81252..d1b5f6fda6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 603cd397b1..6cfcbc498e 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2a25baea90..0f70e2c026 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include @@ -134,7 +134,7 @@ private: class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater { public: - AtmosphereNightUpdater(Resource::TextureManager* textureManager) + AtmosphereNightUpdater(Resource::ImageManager* textureManager) { // we just need a texture, its contents don't really matter mTexture = textureManager->getWarningTexture(); @@ -469,7 +469,7 @@ const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::TextureManager& textureManager) + Sun(osg::Group* parentNode, Resource::ImageManager& textureManager) : CelestialBody(parentNode, 1.0f, 1) , mUpdater(new Updater) { @@ -602,7 +602,7 @@ private: return oqn; } - void createSunFlash(Resource::TextureManager& textureManager) + void createSunFlash(Resource::ImageManager& textureManager) { osg::ref_ptr tex (new osg::Texture2D(textureManager.getImage("textures/tx_sun_flash_grey_05.dds"))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -932,7 +932,7 @@ public: Type_Secunda }; - Moon(osg::Group* parentNode, Resource::TextureManager& textureManager, float scaleFactor, Type type) + Moon(osg::Group* parentNode, Resource::ImageManager& textureManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase_Unspecified) @@ -1001,7 +1001,7 @@ public: private: struct Updater : public SceneUtil::StateSetUpdater { - Resource::TextureManager& mTextureManager; + Resource::ImageManager& mTextureManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; @@ -1009,7 +1009,7 @@ private: osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; - Updater(Resource::TextureManager& textureManager) + Updater(Resource::ImageManager& textureManager) : mTextureManager(textureManager) , mPhaseTex() , mCircleTex() diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index df6a0497f1..3c8d1408af 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include namespace MWRender diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index ff31564119..7d1221a53e 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 089779eda8..0b54ddeb73 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,7 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager texturemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache + scenemanager keyframemanager imagemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache ) add_component_dir (sceneutil diff --git a/components/myguiplatform/myguiplatform.cpp b/components/myguiplatform/myguiplatform.cpp index 22b88438f6..491af84868 100644 --- a/components/myguiplatform/myguiplatform.cpp +++ b/components/myguiplatform/myguiplatform.cpp @@ -7,7 +7,7 @@ namespace osgMyGUI { -Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::TextureManager *textureManager, float uiScalingFactor) +Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::ImageManager *textureManager, float uiScalingFactor) : mRenderManager(nullptr) , mDataManager(nullptr) , mLogManager(nullptr) diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp index 90d45ce204..b3f9597982 100644 --- a/components/myguiplatform/myguiplatform.hpp +++ b/components/myguiplatform/myguiplatform.hpp @@ -13,7 +13,7 @@ namespace osg } namespace Resource { - class TextureManager; + class ImageManager; } namespace MyGUI { @@ -30,7 +30,7 @@ namespace osgMyGUI class Platform { public: - Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::TextureManager* textureManager, float uiScalingFactor); + Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* textureManager, float uiScalingFactor); ~Platform(); diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index f5bebd560f..23d004f478 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -15,7 +15,7 @@ #include -#include +#include #include "myguitexture.hpp" @@ -353,7 +353,7 @@ void OSGVertexBuffer::create() // --------------------------------------------------------------------------- -RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor) +RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* textureManager, float scalingFactor) : mViewer(viewer) , mSceneRoot(sceneroot) , mTextureManager(textureManager) diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index f2251cdb04..b1aa0d3f0f 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -7,7 +7,7 @@ namespace Resource { - class TextureManager; + class ImageManager; } namespace osgViewer @@ -33,7 +33,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget osg::ref_ptr mViewer; osg::ref_ptr mSceneRoot; osg::ref_ptr mDrawable; - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mTextureManager; MyGUI::IntSize mViewSize; bool mUpdate; @@ -54,7 +54,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget void destroyAllResources(); public: - RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor); + RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* textureManager, float scalingFactor); virtual ~RenderManager(); void initialise(); diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 3560a3ae3b..904ed6abf7 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -5,12 +5,12 @@ #include -#include +#include namespace osgMyGUI { - OSGTexture::OSGTexture(const std::string &name, Resource::TextureManager* textureManager) + OSGTexture::OSGTexture(const std::string &name, Resource::ImageManager* textureManager) : mName(name) , mTextureManager(textureManager) , mFormat(MyGUI::PixelFormat::Unknow) diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index de385e94d6..21bc899928 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -13,7 +13,7 @@ namespace osg namespace Resource { - class TextureManager; + class ImageManager; } namespace osgMyGUI @@ -21,7 +21,7 @@ namespace osgMyGUI class OSGTexture : public MyGUI::ITexture { std::string mName; - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mTextureManager; osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; @@ -30,7 +30,7 @@ namespace osgMyGUI size_t mNumElemBytes; public: - OSGTexture(const std::string &name, Resource::TextureManager* textureManager); + OSGTexture(const std::string &name, Resource::ImageManager* textureManager); OSGTexture(osg::Texture2D* texture); virtual ~OSGTexture(); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 190fd020fe..d09c34aa58 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -11,7 +11,7 @@ // resource #include #include -#include +#include // skel #include @@ -343,7 +343,7 @@ namespace NifOsg } } - osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::TextureManager* textureManager) + osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* textureManager) { if (nif->numRoots() < 1) nif->fail("Found no root nodes"); @@ -369,7 +369,7 @@ namespace NifOsg return created; } - void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* textureManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { osg::ref_ptr node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); @@ -682,7 +682,7 @@ namespace NifOsg } } - void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, osg::StateSet *stateset, int animflags) + void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, osg::StateSet *stateset, int animflags) { for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -1244,7 +1244,7 @@ namespace NifOsg } void handleProperty(const Nif::Property *property, - osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, std::vector& boundTextures, int animflags) { switch (property->recType) { @@ -1543,7 +1543,7 @@ namespace NifOsg }; - osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager) + osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* textureManager) { LoaderImpl impl(file->getFilename()); return impl.load(file, textureManager); diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index e15df53028..afc912ed38 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -15,7 +15,7 @@ namespace osg namespace Resource { - class TextureManager; + class ImageManager; } namespace NifOsg @@ -62,7 +62,7 @@ namespace NifOsg { public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton if so. - static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager); + static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* textureManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target); diff --git a/components/resource/texturemanager.cpp b/components/resource/imagemanager.cpp similarity index 94% rename from components/resource/texturemanager.cpp rename to components/resource/imagemanager.cpp index b3f23ab406..8b14e72376 100644 --- a/components/resource/texturemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -1,4 +1,4 @@ -#include "texturemanager.hpp" +#include "imagemanager.hpp" #include #include @@ -42,7 +42,7 @@ namespace namespace Resource { - TextureManager::TextureManager(const VFS::Manager *vfs) + ImageManager::ImageManager(const VFS::Manager *vfs) : mVFS(vfs) , mWarningTexture(createWarningTexture()) , mWarningImage(mWarningTexture->getImage()) @@ -50,7 +50,7 @@ namespace Resource { } - TextureManager::~TextureManager() + ImageManager::~ImageManager() { } @@ -88,7 +88,7 @@ namespace Resource return true; } - osg::ref_ptr TextureManager::getImage(const std::string &filename) + osg::ref_ptr ImageManager::getImage(const std::string &filename) { std::string normalized = filename; mVFS->normalizeFilename(normalized); @@ -137,7 +137,7 @@ namespace Resource } } - osg::Texture2D* TextureManager::getWarningTexture() + osg::Texture2D* ImageManager::getWarningTexture() { return mWarningTexture.get(); } diff --git a/components/resource/texturemanager.hpp b/components/resource/imagemanager.hpp similarity index 83% rename from components/resource/texturemanager.hpp rename to components/resource/imagemanager.hpp index 0290d2340b..14d0b673e3 100644 --- a/components/resource/texturemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -27,11 +27,11 @@ namespace Resource { /// @brief Handles loading/caching of Images. - class TextureManager + class ImageManager { public: - TextureManager(const VFS::Manager* vfs); - ~TextureManager(); + ImageManager(const VFS::Manager* vfs); + ~ImageManager(); /// Create or retrieve an Image /// Returns the dummy image if the given image is not found. @@ -51,8 +51,8 @@ namespace Resource osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; - TextureManager(const TextureManager&); - void operator = (const TextureManager&); + ImageManager(const ImageManager&); + void operator = (const ImageManager&); }; } diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 2ce8d22e6a..89a4b2cb3b 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -1,7 +1,7 @@ #include "resourcesystem.hpp" #include "scenemanager.hpp" -#include "texturemanager.hpp" +#include "imagemanager.hpp" #include "niffilemanager.hpp" #include "keyframemanager.hpp" @@ -13,7 +13,7 @@ namespace Resource { mNifFileManager.reset(new NifFileManager(vfs)); mKeyframeManager.reset(new KeyframeManager(vfs)); - mTextureManager.reset(new TextureManager(vfs)); + mTextureManager.reset(new ImageManager(vfs)); mSceneManager.reset(new SceneManager(vfs, mTextureManager.get(), mNifFileManager.get())); } @@ -27,7 +27,7 @@ namespace Resource return mSceneManager.get(); } - TextureManager* ResourceSystem::getTextureManager() + ImageManager* ResourceSystem::getTextureManager() { return mTextureManager.get(); } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 3e1a793cac..7c4d83b7da 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -12,7 +12,7 @@ namespace Resource { class SceneManager; - class TextureManager; + class ImageManager; class NifFileManager; class KeyframeManager; @@ -26,7 +26,7 @@ namespace Resource ~ResourceSystem(); SceneManager* getSceneManager(); - TextureManager* getTextureManager(); + ImageManager* getTextureManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); @@ -37,7 +37,7 @@ namespace Resource private: std::auto_ptr mSceneManager; - std::auto_ptr mTextureManager; + std::auto_ptr mTextureManager; std::auto_ptr mNifFileManager; std::auto_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e3f38e8867..287186887b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -24,7 +24,7 @@ #include #include -#include "texturemanager.hpp" +#include "imagemanager.hpp" #include "niffilemanager.hpp" namespace @@ -227,7 +227,7 @@ namespace Resource - SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager) + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* textureManager, Resource::NifFileManager* nifFileManager) : mVFS(vfs) , mTextureManager(textureManager) , mNifFileManager(nifFileManager) @@ -249,7 +249,7 @@ namespace Resource class ImageReadCallback : public osgDB::ReadFileCallback { public: - ImageReadCallback(Resource::TextureManager* textureMgr) + ImageReadCallback(Resource::ImageManager* textureMgr) : mTextureManager(textureMgr) { } @@ -267,7 +267,7 @@ namespace Resource } private: - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mTextureManager; }; std::string getFileExtension(const std::string& file) @@ -278,7 +278,7 @@ namespace Resource return std::string(); } - osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::TextureManager* textureMgr, Resource::NifFileManager* nifFileManager) + osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* textureMgr, Resource::NifFileManager* nifFileManager) { std::string ext = getFileExtension(normalizedFilename); if (ext == "nif") @@ -408,7 +408,7 @@ namespace Resource return mVFS; } - Resource::TextureManager* SceneManager::getTextureManager() + Resource::ImageManager* SceneManager::getTextureManager() { return mTextureManager; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index ae5fa2db6e..6eadf7b45e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -10,7 +10,7 @@ namespace Resource { - class TextureManager; + class ImageManager; class NifFileManager; } @@ -36,7 +36,7 @@ namespace Resource class SceneManager { public: - SceneManager(const VFS::Manager* vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager); + SceneManager(const VFS::Manager* vfs, Resource::ImageManager* textureManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); /// Get a read-only copy of this scene "template" @@ -70,7 +70,7 @@ namespace Resource const VFS::Manager* getVFS() const; - Resource::TextureManager* getTextureManager(); + Resource::ImageManager* getTextureManager(); /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); @@ -89,7 +89,7 @@ namespace Resource private: const VFS::Manager* mVFS; - Resource::TextureManager* mTextureManager; + Resource::ImageManager* mTextureManager; Resource::NifFileManager* mNifFileManager; osg::Texture::FilterMode mMinFilter; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 58675a7140..094023f388 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include From 5ee3d1698fd10584b07213f74c0a2a97b0fb06a9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:05:37 +0100 Subject: [PATCH 183/765] Remove getWarningTexture in favor of getWarningImage --- apps/openmw/mwrender/sky.cpp | 2 +- components/resource/imagemanager.cpp | 14 +++++--------- components/resource/imagemanager.hpp | 3 +-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0f70e2c026..69fbb23799 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -137,7 +137,7 @@ public: AtmosphereNightUpdater(Resource::ImageManager* textureManager) { // we just need a texture, its contents don't really matter - mTexture = textureManager->getWarningTexture(); + mTexture = new osg::Texture2D(textureManager->getWarningImage()); } void setFade(const float fade) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 8b14e72376..e1c0572c73 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -17,7 +17,7 @@ USE_OSGPLUGIN(jpeg) namespace { - osg::ref_ptr createWarningTexture() + osg::ref_ptr createWarningImage() { osg::ref_ptr warningImage = new osg::Image; @@ -31,10 +31,7 @@ namespace data[3*i+1] = (0); data[3*i+2] = (255); } - - osg::ref_ptr warningTexture = new osg::Texture2D; - warningTexture->setImage(warningImage); - return warningTexture; + return warningImage; } } @@ -44,8 +41,7 @@ namespace Resource ImageManager::ImageManager(const VFS::Manager *vfs) : mVFS(vfs) - , mWarningTexture(createWarningTexture()) - , mWarningImage(mWarningTexture->getImage()) + , mWarningImage(createWarningImage()) , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) { } @@ -137,9 +133,9 @@ namespace Resource } } - osg::Texture2D* ImageManager::getWarningTexture() + osg::Image *ImageManager::getWarningImage() { - return mWarningTexture.get(); + return mWarningImage; } } diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index 14d0b673e3..ee0c0f2e2f 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -39,7 +39,7 @@ namespace Resource const VFS::Manager* getVFS() { return mVFS; } - osg::Texture2D* getWarningTexture(); + osg::Image* getWarningImage(); private: const VFS::Manager* mVFS; @@ -47,7 +47,6 @@ namespace Resource // TODO: use ObjectCache std::map > mImages; - osg::ref_ptr mWarningTexture; osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; From 9e53e12c70f38c6de9d2ea515a58746c413dd753 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:10:27 +0100 Subject: [PATCH 184/765] More renaming of TextureManager -> ImageManager --- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 42 +++++++++---------- apps/openmw/mwrender/util.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- components/myguiplatform/myguiplatform.cpp | 4 +- components/myguiplatform/myguiplatform.hpp | 2 +- .../myguiplatform/myguirendermanager.cpp | 6 +-- .../myguiplatform/myguirendermanager.hpp | 4 +- components/myguiplatform/myguitexture.cpp | 12 +++--- components/myguiplatform/myguitexture.hpp | 4 +- components/nifosg/nifloader.cpp | 32 +++++++------- components/nifosg/nifloader.hpp | 2 +- components/resource/imagemanager.hpp | 4 +- components/resource/resourcesystem.cpp | 8 ++-- components/resource/resourcesystem.hpp | 4 +- components/resource/scenemanager.cpp | 26 ++++++------ components/resource/scenemanager.hpp | 6 +-- components/terrain/terraingrid.cpp | 2 +- 20 files changed, 85 insertions(+), 85 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index d053d8bfde..03fbc92382 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -194,7 +194,7 @@ namespace MWGui , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale); + mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); mGuiPlatform->initialise(resourcePath, logpath); mGui = new MyGUI::Gui; @@ -2015,7 +2015,7 @@ namespace MWGui continue; std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; - osg::ref_ptr image = mResourceSystem->getTextureManager()->getImage(tex_name); + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(tex_name); if(image.valid()) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 2c5831dae1..b7c0c0a365 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1059,7 +1059,7 @@ namespace MWRender stream << i; stream << ".dds"; - osg::ref_ptr image = mResourceSystem->getTextureManager()->getImage(stream.str()); + osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); osg::ref_ptr tex (new osg::Texture2D(image)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 6cfcbc498e..f43f1ee5c3 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -40,7 +40,7 @@ namespace { std::ostringstream texname; texname << "textures/water/" << tex << std::setw(2) << std::setfill('0') << i << ".dds"; - osg::ref_ptr tex (new osg::Texture2D(resourceSystem->getTextureManager()->getImage(texname.str()))); + osg::ref_ptr tex (new osg::Texture2D(resourceSystem->getImageManager()->getImage(texname.str()))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); resourceSystem->getSceneManager()->applyFilterSettings(tex); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 69fbb23799..0d3bf1a666 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -134,10 +134,10 @@ private: class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater { public: - AtmosphereNightUpdater(Resource::ImageManager* textureManager) + AtmosphereNightUpdater(Resource::ImageManager* imageManager) { // we just need a texture, its contents don't really matter - mTexture = new osg::Texture2D(textureManager->getWarningImage()); + mTexture = new osg::Texture2D(imageManager->getWarningImage()); } void setFade(const float fade) @@ -469,14 +469,14 @@ const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::ImageManager& textureManager) + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) : CelestialBody(parentNode, 1.0f, 1) , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); mTransform->setNodeMask(Mask_Sun); - osg::ref_ptr sunTex (new osg::Texture2D(textureManager.getImage("textures/tx_sun_05.dds"))); + osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -498,7 +498,7 @@ public: mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); - createSunFlash(textureManager); + createSunFlash(imageManager); createSunGlare(); } @@ -602,9 +602,9 @@ private: return oqn; } - void createSunFlash(Resource::ImageManager& textureManager) + void createSunFlash(Resource::ImageManager& imageManager) { - osg::ref_ptr tex (new osg::Texture2D(textureManager.getImage("textures/tx_sun_flash_grey_05.dds"))); + osg::ref_ptr tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds"))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -932,11 +932,11 @@ public: Type_Secunda }; - Moon(osg::Group* parentNode, Resource::ImageManager& textureManager, float scaleFactor, Type type) + Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase_Unspecified) - , mUpdater(new Updater(textureManager)) + , mUpdater(new Updater(imageManager)) { setPhase(MoonState::Phase_Full); setVisible(true); @@ -1001,7 +1001,7 @@ public: private: struct Updater : public SceneUtil::StateSetUpdater { - Resource::ImageManager& mTextureManager; + Resource::ImageManager& mImageManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; @@ -1009,8 +1009,8 @@ private: osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; - Updater(Resource::ImageManager& textureManager) - : mTextureManager(textureManager) + Updater(Resource::ImageManager& imageManager) + : mImageManager(imageManager) , mPhaseTex() , mCircleTex() , mTransparency(1.0f) @@ -1055,10 +1055,10 @@ private: void setTextures(const std::string& phaseTex, const std::string& circleTex) { - mPhaseTex = new osg::Texture2D(mTextureManager.getImage(phaseTex)); + mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mCircleTex = new osg::Texture2D(mTextureManager.getImage(circleTex)); + mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -1165,14 +1165,14 @@ void SkyManager::create() atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); ModVertexAlphaVisitor modStars(2); atmosphereNight->accept(modStars); - mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getTextureManager()); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getTextureManager())); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback(); - mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); mCloudNode = new osg::PositionAttitudeTransform; mEarlyRenderBinRoot->addChild(mCloudNode); @@ -1347,7 +1347,7 @@ void SkyManager::createRain() osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage("textures/tx_raindrop_01.dds"))); + osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds"))); raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -1557,7 +1557,7 @@ void SkyManager::setWeather(const WeatherResult& weather) std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage(texture))); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); @@ -1572,7 +1572,7 @@ void SkyManager::setWeather(const WeatherResult& weather) { std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getTextureManager()->getImage(texture))); + osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 3c8d1408af..0e1eaf0f4b 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -15,7 +15,7 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou return; std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); // Not sure if wrap settings should be pulled from the overridden texture? - osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getTextureManager()->getImage(correctedTexture)); + osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7d1221a53e..0e4f1a974b 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -556,7 +556,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { std::ostringstream texname; texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; - osg::ref_ptr tex (new osg::Texture2D(mResourceSystem->getTextureManager()->getImage(texname.str()))); + osg::ref_ptr tex (new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); textures.push_back(tex); diff --git a/components/myguiplatform/myguiplatform.cpp b/components/myguiplatform/myguiplatform.cpp index 491af84868..dfb2e6539d 100644 --- a/components/myguiplatform/myguiplatform.cpp +++ b/components/myguiplatform/myguiplatform.cpp @@ -7,14 +7,14 @@ namespace osgMyGUI { -Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::ImageManager *textureManager, float uiScalingFactor) +Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::ImageManager *imageManager, float uiScalingFactor) : mRenderManager(nullptr) , mDataManager(nullptr) , mLogManager(nullptr) , mLogFacility(nullptr) { mLogManager = new MyGUI::LogManager(); - mRenderManager = new RenderManager(viewer, guiRoot, textureManager, uiScalingFactor); + mRenderManager = new RenderManager(viewer, guiRoot, imageManager, uiScalingFactor); mDataManager = new DataManager(); } diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp index b3f9597982..5ffbe0be70 100644 --- a/components/myguiplatform/myguiplatform.hpp +++ b/components/myguiplatform/myguiplatform.hpp @@ -30,7 +30,7 @@ namespace osgMyGUI class Platform { public: - Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* textureManager, float uiScalingFactor); + Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* imageManager, float uiScalingFactor); ~Platform(); diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 23d004f478..5d2e3a9aed 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -353,10 +353,10 @@ void OSGVertexBuffer::create() // --------------------------------------------------------------------------- -RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* textureManager, float scalingFactor) +RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor) : mViewer(viewer) , mSceneRoot(sceneroot) - , mTextureManager(textureManager) + , mImageManager(imageManager) , mUpdate(false) , mIsInitialise(false) , mInvScalingFactor(1.f) @@ -523,7 +523,7 @@ MyGUI::ITexture* RenderManager::createTexture(const std::string &name) mTextures.erase(item); } - OSGTexture* texture = new OSGTexture(name, mTextureManager); + OSGTexture* texture = new OSGTexture(name, mImageManager); mTextures.insert(std::make_pair(name, texture)); return texture; } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index b1aa0d3f0f..4a0aae3cda 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -33,7 +33,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget osg::ref_ptr mViewer; osg::ref_ptr mSceneRoot; osg::ref_ptr mDrawable; - Resource::ImageManager* mTextureManager; + Resource::ImageManager* mImageManager; MyGUI::IntSize mViewSize; bool mUpdate; @@ -54,7 +54,7 @@ class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget void destroyAllResources(); public: - RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* textureManager, float scalingFactor); + RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor); virtual ~RenderManager(); void initialise(); diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 904ed6abf7..2ba78c26b3 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -10,9 +10,9 @@ namespace osgMyGUI { - OSGTexture::OSGTexture(const std::string &name, Resource::ImageManager* textureManager) + OSGTexture::OSGTexture(const std::string &name, Resource::ImageManager* imageManager) : mName(name) - , mTextureManager(textureManager) + , mImageManager(imageManager) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) , mNumElemBytes(0) @@ -20,7 +20,7 @@ namespace osgMyGUI } OSGTexture::OSGTexture(osg::Texture2D *texture) - : mTextureManager(NULL) + : mImageManager(NULL) , mTexture(texture) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) @@ -83,10 +83,10 @@ namespace osgMyGUI void OSGTexture::loadFromFile(const std::string &fname) { - if (!mTextureManager) - throw std::runtime_error("No texturemanager set"); + if (!mImageManager) + throw std::runtime_error("No imagemanager set"); - mTexture = new osg::Texture2D(mTextureManager->getImage(fname)); + mTexture = new osg::Texture2D(mImageManager->getImage(fname)); mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); // disable mip-maps diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index 21bc899928..101e2135bf 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -21,7 +21,7 @@ namespace osgMyGUI class OSGTexture : public MyGUI::ITexture { std::string mName; - Resource::ImageManager* mTextureManager; + Resource::ImageManager* mImageManager; osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; @@ -30,7 +30,7 @@ namespace osgMyGUI size_t mNumElemBytes; public: - OSGTexture(const std::string &name, Resource::ImageManager* textureManager); + OSGTexture(const std::string &name, Resource::ImageManager* imageManager); OSGTexture(osg::Texture2D* texture); virtual ~OSGTexture(); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index d09c34aa58..6c6061063e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -343,7 +343,7 @@ namespace NifOsg } } - osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* textureManager) + osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* imageManager) { if (nif->numRoots() < 1) nif->fail("Found no root nodes"); @@ -356,7 +356,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, textureManager, std::vector(), 0, 0, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, 0, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -369,13 +369,13 @@ namespace NifOsg return created; } - void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, std::vector& boundTextures, int animflags) + void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* textureManager, + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { osg::ref_ptr node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); @@ -542,7 +542,7 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, textureManager, boundTextures, animflags); + applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) { @@ -588,7 +588,7 @@ namespace NifOsg { if(!children[i].empty()) { - handleNode(children[i].getPtr(), node, textureManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); + handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); } } } @@ -682,7 +682,7 @@ namespace NifOsg } } - void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, osg::StateSet *stateset, int animflags) + void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet *stateset, int animflags) { for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -708,8 +708,8 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); - osg::ref_ptr texture (new osg::Texture2D(textureManager->getImage(filename))); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + osg::ref_ptr texture (new osg::Texture2D(imageManager->getImage(filename))); texture->setWrap(osg::Texture::WRAP_S, wrapS); texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); @@ -1244,7 +1244,7 @@ namespace NifOsg } void handleProperty(const Nif::Property *property, - osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* textureManager, std::vector& boundTextures, int animflags) + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { switch (property->recType) { @@ -1372,14 +1372,14 @@ namespace NifOsg continue; } - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); unsigned int clamp = static_cast(tex.clamp); int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d (new osg::Texture2D(textureManager->getImage(filename))); + osg::ref_ptr texture2d (new osg::Texture2D(imageManager->getImage(filename))); texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); @@ -1423,7 +1423,7 @@ namespace NifOsg boundTextures.push_back(tex.uvSet); } - handleTextureControllers(texprop, composite, textureManager, stateset, animflags); + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } break; } @@ -1543,10 +1543,10 @@ namespace NifOsg }; - osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* textureManager) + osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager) { LoaderImpl impl(file->getFilename()); - return impl.load(file, textureManager); + return impl.load(file, imageManager); } void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target) diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index afc912ed38..d2d5e7b2d8 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -62,7 +62,7 @@ namespace NifOsg { public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton if so. - static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* textureManager); + static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target); diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index ee0c0f2e2f..3b2bacc131 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H -#define OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H +#ifndef OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H #include #include diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 89a4b2cb3b..d39a723d6b 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -13,8 +13,8 @@ namespace Resource { mNifFileManager.reset(new NifFileManager(vfs)); mKeyframeManager.reset(new KeyframeManager(vfs)); - mTextureManager.reset(new ImageManager(vfs)); - mSceneManager.reset(new SceneManager(vfs, mTextureManager.get(), mNifFileManager.get())); + mImageManager.reset(new ImageManager(vfs)); + mSceneManager.reset(new SceneManager(vfs, mImageManager.get(), mNifFileManager.get())); } ResourceSystem::~ResourceSystem() @@ -27,9 +27,9 @@ namespace Resource return mSceneManager.get(); } - ImageManager* ResourceSystem::getTextureManager() + ImageManager* ResourceSystem::getImageManager() { - return mTextureManager.get(); + return mImageManager.get(); } NifFileManager* ResourceSystem::getNifFileManager() diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 7c4d83b7da..13a96e8c7f 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -26,7 +26,7 @@ namespace Resource ~ResourceSystem(); SceneManager* getSceneManager(); - ImageManager* getTextureManager(); + ImageManager* getImageManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); @@ -37,7 +37,7 @@ namespace Resource private: std::auto_ptr mSceneManager; - std::auto_ptr mTextureManager; + std::auto_ptr mImageManager; std::auto_ptr mNifFileManager; std::auto_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 287186887b..dd48478778 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -227,9 +227,9 @@ namespace Resource - SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* textureManager, Resource::NifFileManager* nifFileManager) + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : mVFS(vfs) - , mTextureManager(textureManager) + , mImageManager(imageManager) , mNifFileManager(nifFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMagFilter(osg::Texture::LINEAR) @@ -249,8 +249,8 @@ namespace Resource class ImageReadCallback : public osgDB::ReadFileCallback { public: - ImageReadCallback(Resource::ImageManager* textureMgr) - : mTextureManager(textureMgr) + ImageReadCallback(Resource::ImageManager* imageMgr) + : mImageManager(imageMgr) { } @@ -258,7 +258,7 @@ namespace Resource { try { - return osgDB::ReaderWriter::ReadResult(mTextureManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { @@ -267,7 +267,7 @@ namespace Resource } private: - Resource::ImageManager* mTextureManager; + Resource::ImageManager* mImageManager; }; std::string getFileExtension(const std::string& file) @@ -278,11 +278,11 @@ namespace Resource return std::string(); } - osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* textureMgr, Resource::NifFileManager* nifFileManager) + osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { std::string ext = getFileExtension(normalizedFilename); if (ext == "nif") - return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), textureMgr); + return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); @@ -297,7 +297,7 @@ namespace Resource // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system instead of the osgDB. // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. // but findFileCallback does not support virtual files, so we can't implement it. - options->setReadFileCallback(new ImageReadCallback(textureMgr)); + options->setReadFileCallback(new ImageReadCallback(imageManager)); osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); if (!result.success()) @@ -323,7 +323,7 @@ namespace Resource { Files::IStreamPtr file = mVFS->get(normalized); - loaded = load(file, normalized, mTextureManager, mNifFileManager); + loaded = load(file, normalized, mImageManager, mNifFileManager); } catch (std::exception& e) { @@ -336,7 +336,7 @@ namespace Resource { std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead" << std::endl; Files::IStreamPtr file = mVFS->get(normalized); - loaded = load(file, normalized, mTextureManager, mNifFileManager); + loaded = load(file, normalized, mImageManager, mNifFileManager); break; } } @@ -408,9 +408,9 @@ namespace Resource return mVFS; } - Resource::ImageManager* SceneManager::getTextureManager() + Resource::ImageManager* SceneManager::getImageManager() { - return mTextureManager; + return mImageManager; } void SceneManager::setParticleSystemMask(unsigned int mask) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 6eadf7b45e..3dd37111d9 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -36,7 +36,7 @@ namespace Resource class SceneManager { public: - SceneManager(const VFS::Manager* vfs, Resource::ImageManager* textureManager, Resource::NifFileManager* nifFileManager); + SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); /// Get a read-only copy of this scene "template" @@ -70,7 +70,7 @@ namespace Resource const VFS::Manager* getVFS() const; - Resource::ImageManager* getTextureManager(); + Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); @@ -89,7 +89,7 @@ namespace Resource private: const VFS::Manager* mVFS; - Resource::ImageManager* mTextureManager; + Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; osg::Texture::FilterMode mMinFilter; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 094023f388..2efbf1b9ea 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -142,7 +142,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu osg::ref_ptr tex = mTextureCache[it->mDiffuseMap]; if (!tex) { - tex = new osg::Texture2D(mResourceSystem->getTextureManager()->getImage(it->mDiffuseMap)); + tex = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mTextureCache[it->mDiffuseMap] = tex; From 499beda6656e37b45aa35549126023fd4898ceb2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:20:13 +0100 Subject: [PATCH 185/765] Clear terrain texture cache before applying filter settings --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d1b5f6fda6..9b7ced4b58 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -783,6 +783,9 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { + if (mTerrain.get()) + mTerrain->clearCache(); + mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), From 9e5225bb6f952b50402063783374ffcd7a91be23 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:21:54 +0100 Subject: [PATCH 186/765] Do not unref a Texture's image data after applying it --- apps/openmw/engine.cpp | 2 +- components/terrain/terraingrid.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d96bf16e4e..d30d0961aa 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -448,7 +448,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); - mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(true); + mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 2efbf1b9ea..eb085ca426 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -166,10 +166,6 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } - // SharedStatemanager->share(textureCompileDummy); - - // Remove unrefImageDataAfterApply for better state sharing - // use texture coordinates for both texture units, the layer texture and blend texture for (unsigned int i=0; i<2; ++i) geometry->setTexCoordArray(i, mCache.getUVBuffer()); From a72af4a1a38e9a39057dce2951cd6a0817025810 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:25:01 +0100 Subject: [PATCH 187/765] cout that should be cerr --- apps/openmw/mwworld/localscripts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 7ccc213c45..f0174ac522 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -101,7 +101,7 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) if (iter->second==ptr) { - std::cout << "warning, tried to add local script twice for " << ptr.getCellRef().getRefId() << std::endl; + std::cerr << "warning, tried to add local script twice for " << ptr.getCellRef().getRefId() << std::endl; remove(ptr); break; } From 6c1c653cbab3fe0228303ccf50c40c93dfa9c370 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:31:59 +0100 Subject: [PATCH 188/765] Use the osgDB::ObjectCache in ImageManager Should be thread safe now. --- components/resource/imagemanager.cpp | 16 ++++++++++++---- components/resource/imagemanager.hpp | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index e1c0572c73..bd422fe2a6 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -6,6 +6,8 @@ #include +#include "objectcache.hpp" + #ifdef OSG_LIBRARY_STATIC // This list of plugins should match with the list in the top-level CMakelists.txt. USE_OSGPLUGIN(png) @@ -41,6 +43,7 @@ namespace Resource ImageManager::ImageManager(const VFS::Manager *vfs) : mVFS(vfs) + , mCache(new osgDB::ObjectCache) , mWarningImage(createWarningImage()) , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) { @@ -88,9 +91,10 @@ namespace Resource { std::string normalized = filename; mVFS->normalizeFilename(normalized); - std::map >::iterator found = mImages.find(normalized); - if (found != mImages.end()) - return found->second; + + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); else { Files::IStreamPtr stream; @@ -101,6 +105,7 @@ namespace Resource catch (std::exception& e) { std::cerr << "Failed to open image: " << e.what() << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } @@ -112,6 +117,7 @@ namespace Resource if (!reader) { std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } @@ -119,16 +125,18 @@ namespace Resource if (!result.success()) { std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } osg::Image* image = result.getImage(); if (!checkSupported(image, filename)) { + mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } - mImages.insert(std::make_pair(normalized, image)); + mCache->addEntryToObjectCache(normalized, image); return image; } } diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index 3b2bacc131..bc1d7b4e4b 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -21,12 +21,14 @@ namespace VFS namespace osgDB { class Options; + class ObjectCache; } namespace Resource { /// @brief Handles loading/caching of Images. + /// @note May be used from any thread. class ImageManager { public: @@ -44,8 +46,7 @@ namespace Resource private: const VFS::Manager* mVFS; - // TODO: use ObjectCache - std::map > mImages; + osg::ref_ptr mCache; osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; From 909c4d96b64e5fa6f85a959f59964927a28730b6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Feb 2016 23:59:37 +0100 Subject: [PATCH 189/765] Use the osgDB::ObjectCache in BulletShapeManager --- components/resource/bulletshape.cpp | 8 ++++++++ components/resource/bulletshape.hpp | 8 ++++++-- components/resource/bulletshapemanager.cpp | 21 ++++++++++++++------- components/resource/bulletshapemanager.hpp | 8 ++++++-- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 0cbc63a229..baff86a794 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -17,6 +17,14 @@ BulletShape::BulletShape() } +BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) + : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) + , mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) + , mCollisionBoxTranslate(copy.mCollisionBoxTranslate) + , mAnimatedShapes(copy.mAnimatedShapes) +{ +} + BulletShape::~BulletShape() { deleteShape(mCollisionShape); diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index cfae27eac4..a007bad310 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include @@ -15,12 +15,15 @@ namespace Resource { class BulletShapeInstance; - class BulletShape : public osg::Referenced + class BulletShape : public osg::Object { public: BulletShape(); + BulletShape(const BulletShape& copy, const osg::CopyOp& copyop); virtual ~BulletShape(); + META_Object(Resource, BulletShape) + btCollisionShape* mCollisionShape; // Used for actors. Note, ideally actors would use a separate loader - as it is @@ -42,6 +45,7 @@ namespace Resource btCollisionShape* getCollisionShape(); private: + void deleteShape(btCollisionShape* shape); }; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 4cbe62f3c3..2ab7b243aa 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -13,6 +13,7 @@ #include "bulletshape.hpp" #include "scenemanager.hpp" #include "niffilemanager.hpp" +#include "objectcache.hpp" namespace Resource @@ -99,6 +100,7 @@ BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sc : mVFS(vfs) , mSceneManager(sceneMgr) , mNifFileManager(nifFileManager) + , mCache(new osgDB::ObjectCache) { } @@ -114,8 +116,10 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: mVFS->normalizeFilename(normalized); osg::ref_ptr shape; - Index::iterator it = mIndex.find(normalized); - if (it == mIndex.end()) + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + shape = osg::ref_ptr(static_cast(obj.get())); + else { size_t extPos = normalized.find_last_of('.'); std::string ext; @@ -137,16 +141,19 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: node->accept(visitor); shape = visitor.getShape(); if (!shape) + { + mCache->addEntryToObjectCache(normalized, NULL); return osg::ref_ptr(); + } } - mIndex[normalized] = shape; + mCache->addEntryToObjectCache(normalized, shape); } + + if (shape) + return shape->makeInstance(); else - shape = it->second; - - osg::ref_ptr instance = shape->makeInstance(); - return instance; + return osg::ref_ptr(); } } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index ac1523495c..d9293896b0 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -13,6 +13,11 @@ namespace VFS class Manager; } +namespace osgDB +{ + class ObjectCache; +} + namespace Resource { class SceneManager; @@ -34,8 +39,7 @@ namespace Resource SceneManager* mSceneManager; NifFileManager* mNifFileManager; - typedef std::map > Index; - Index mIndex; + osg::ref_ptr mCache; }; } From ea1efaac0cc5b7d228a90a99757594fe25e10cab Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Feb 2016 00:15:12 +0100 Subject: [PATCH 190/765] Use the osgDB::ObjectCache in SceneManager, cleanup --- components/resource/bulletshapemanager.hpp | 3 +++ components/resource/keyframemanager.hpp | 2 +- components/resource/niffilemanager.hpp | 2 +- components/resource/scenemanager.cpp | 27 +++++++++------------- components/resource/scenemanager.hpp | 23 ++++++++++-------- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index d9293896b0..c8db8849e7 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -26,6 +26,9 @@ namespace Resource class BulletShape; class BulletShapeInstance; + /// Handles loading, caching and "instancing" of bullet shapes. + /// A shape 'instance' is a clone of another shape, with the goal of setting a different scale on this instance. + /// @note May be used from any thread. class BulletShapeManager { public: diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 5032d0e389..5a5cb36284 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -23,6 +23,7 @@ namespace Resource { /// @brief Managing of keyframe resources + /// @note May be used from any thread. class KeyframeManager { public: @@ -32,7 +33,6 @@ namespace Resource void clearCache(); /// Retrieve a read-only keyframe resource by name (case-insensitive). - /// @note This method is safe to call from any thread. /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 90ad9fc294..4551cf2275 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -19,7 +19,7 @@ namespace Resource { /// @brief Handles caching of NIFFiles. - /// @note The NifFileManager is completely thread safe. + /// @note May be used from any thread. class NifFileManager { public: diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index dd48478778..95e03b3894 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -26,10 +26,12 @@ #include "imagemanager.hpp" #include "niffilemanager.hpp" +#include "objectcache.hpp" namespace { + /// @todo Do this in updateCallback so that animations are accounted for. class InitWorldSpaceParticlesVisitor : public osg::NodeVisitor { public: @@ -236,6 +238,7 @@ namespace Resource , mMaxAnisotropy(1) , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) + , mCache(new osgDB::ObjectCache) { } @@ -315,8 +318,10 @@ namespace Resource std::string normalized = name; mVFS->normalizeFilename(normalized); - Index::iterator it = mIndex.find(normalized); - if (it == mIndex.end()) + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); + else { osg::ref_ptr loaded; try @@ -357,11 +362,9 @@ namespace Resource if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); - mIndex[normalized] = loaded; + mCache->addEntryToObjectCache(normalized, loaded); return loaded; } - else - return it->second; } osg::ref_ptr SceneManager::createInstance(const std::string &name) @@ -386,10 +389,7 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { - for (Index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) - { - it->second->releaseGLObjects(state); - } + mCache->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) @@ -458,6 +458,8 @@ namespace Resource mMagFilter = mag; mMaxAnisotropy = std::max(1, maxAnisotropy); + mCache->clear(); + SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); SetFilterSettingsVisitor setFilterSettingsVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); if (viewer && viewer->getSceneData()) @@ -466,13 +468,6 @@ namespace Resource viewer->getSceneData()->accept(setFilterSettingsVisitor); } - for (Index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) - { - osg::Node* node = it->second; - node->accept(setFilterSettingsControllerVisitor); - node->accept(setFilterSettingsVisitor); - } - if(viewer) viewer->startThreading(); } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 3dd37111d9..32bd806603 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -24,6 +24,11 @@ namespace osgUtil class IncrementalCompileOperation; } +namespace osgDB +{ + class ObjectCache; +} + namespace osgViewer { class Viewer; @@ -32,7 +37,8 @@ namespace osgViewer namespace Resource { - /// @brief Handles loading and caching of scenes, e.g. NIF files + /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files + /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. class SceneManager { public: @@ -42,20 +48,24 @@ namespace Resource /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. + /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name); /// Create an instance of the given scene template /// @see getTemplate + /// @note Thread safe. osg::ref_ptr createInstance(const std::string& name); /// Create an instance of the given scene template and immediately attach it to a parent node /// @see getTemplate + /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. osg::ref_ptr createInstance(const std::string& name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node /// @note You should have the parentNode in its intended position before calling this method, /// so that world space particles of the \a instance get transformed correctly. /// @note Assumes the given instance was not attached to any parents before. + /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. void attachTo(osg::Node* instance, osg::Group* parentNode) const; /// Manually release created OpenGL objects for the given graphics context. This may be required @@ -75,11 +85,12 @@ namespace Resource /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); + /// @param viewer used to apply the new filter settings to the existing scene graph. If there is no scene yet, you can pass a NULL viewer. void setFilterSettings(const std::string &magfilter, const std::string &minfilter, const std::string &mipmap, int maxAnisotropy, osgViewer::Viewer *viewer); - /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate / createInstance) + /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate or createInstance) /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. void applyFilterSettings (osg::Texture* tex); @@ -101,16 +112,10 @@ namespace Resource unsigned int mParticleSystemMask; - // observer_ptr? - typedef std::map > Index; - Index mIndex; + osg::ref_ptr mCache; SceneManager(const SceneManager&); void operator = (const SceneManager&); - - /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! - void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); - }; } From c4658a0ef7f119713909a0fd105465a6f61d00ad Mon Sep 17 00:00:00 2001 From: HiPhish Date: Fri, 5 Feb 2016 23:06:18 +0100 Subject: [PATCH 191/765] Add OpenMW CS manual to the repository. The manual depends on Sphinx to build. A makefile and a Windows batch script are included for building, both were automatically generated by Sphinx. I have left all settings at their default and I have tested that HTML and PDF output build properly. --- .gitignore | 1 + AUTHORS.md | 1 + docs/cs-manual/Makefile | 216 +++++++++++ docs/cs-manual/make.bat | 263 +++++++++++++ .../_static/images/chapter-1/add-record.png | Bin 0 -> 106549 bytes .../_static/images/chapter-1/edit-record.png | Bin 0 -> 191171 bytes .../_static/images/chapter-1/new-project.png | Bin 0 -> 70819 bytes .../_static/images/chapter-1/objects.png | Bin 0 -> 97097 bytes .../images/chapter-1/opening-dialogue.png | Bin 0 -> 92302 bytes docs/cs-manual/source/conf.py | 358 ++++++++++++++++++ .../source/files-and-directories.rst | 224 +++++++++++ docs/cs-manual/source/foreword.rst | 21 + docs/cs-manual/source/index.rst | 30 ++ docs/cs-manual/source/starting-dialog.rst | 40 ++ docs/cs-manual/source/tour.rst | 223 +++++++++++ 15 files changed, 1377 insertions(+) create mode 100644 docs/cs-manual/Makefile create mode 100644 docs/cs-manual/make.bat create mode 100644 docs/cs-manual/source/_static/images/chapter-1/add-record.png create mode 100644 docs/cs-manual/source/_static/images/chapter-1/edit-record.png create mode 100644 docs/cs-manual/source/_static/images/chapter-1/new-project.png create mode 100644 docs/cs-manual/source/_static/images/chapter-1/objects.png create mode 100644 docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png create mode 100644 docs/cs-manual/source/conf.py create mode 100644 docs/cs-manual/source/files-and-directories.rst create mode 100644 docs/cs-manual/source/foreword.rst create mode 100644 docs/cs-manual/source/index.rst create mode 100644 docs/cs-manual/source/starting-dialog.rst create mode 100644 docs/cs-manual/source/tour.rst diff --git a/.gitignore b/.gitignore index e1abcaa639..51b8c1f0f4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ prebuilt ## doxygen Doxygen +!docs/cs-manual/Makefile ## ides/editors *~ diff --git a/AUTHORS.md b/AUTHORS.md index 83a625406e..79cb87e64e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -128,6 +128,7 @@ Manual Bodillium Cramal + Alejandro Sanchez (HiPhish) sir_herrbatka Packagers diff --git a/docs/cs-manual/Makefile b/docs/cs-manual/Makefile new file mode 100644 index 0000000000..9d62dc5abb --- /dev/null +++ b/docs/cs-manual/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenMWCSManual.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenMWCSManual.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenMWCSManual" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenMWCSManual" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/cs-manual/make.bat b/docs/cs-manual/make.bat new file mode 100644 index 0000000000..744d600076 --- /dev/null +++ b/docs/cs-manual/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenMWCSManual.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenMWCSManual.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/cs-manual/source/_static/images/chapter-1/add-record.png b/docs/cs-manual/source/_static/images/chapter-1/add-record.png new file mode 100644 index 0000000000000000000000000000000000000000..c0a110b7c584bfae3ea9341ba215babc59a48b98 GIT binary patch literal 106549 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ⅈ5eeooA9Vm;2*?4 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 RG1}jwhdK|+?md0|{{W`xbXWiY literal 0 HcmV?d00001 diff --git a/docs/cs-manual/source/_static/images/chapter-1/new-project.png b/docs/cs-manual/source/_static/images/chapter-1/new-project.png new file mode 100644 index 0000000000000000000000000000000000000000..a5097db9c06f0f98e002e5ff01f6383eb899b87a GIT binary patch literal 70819 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 mR_Yb)1)ARJx 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


    @?MJ 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

    u^{dbw)-FVZUXunkcAm1r6&A$ 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 HfBk;}BOo6T literal 0 HcmV?d00001 diff --git a/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png b/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png new file mode 100644 index 0000000000000000000000000000000000000000..5356c2695616e8c23f117da9d74615d4b3ef38b1 GIT binary patch literal 92302 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 z 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 s)#~-D+H%#iudVR^hkq2@Gx~0QD v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OpenMWCSManualdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'OpenMWCSManual.tex', u'OpenMW CS Manual Documentation', + u'HiPhish', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'openmwcsmanual', u'OpenMW CS Manual Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'OpenMWCSManual', u'OpenMW CS Manual Documentation', + author, 'OpenMWCSManual', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The basename for the epub file. It defaults to the project name. +#epub_basename = project + +# The HTML theme for the epub output. Since the default themes are not +# optimized for small screen space, using the same theme for HTML and epub +# output is usually not wise. This defaults to 'epub', a theme designed to save +# visual space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or 'en' if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files that should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the Pillow. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/cs-manual/source/files-and-directories.rst b/docs/cs-manual/source/files-and-directories.rst new file mode 100644 index 0000000000..34680fa945 --- /dev/null +++ b/docs/cs-manual/source/files-and-directories.rst @@ -0,0 +1,224 @@ +Files and Directories +##################### + +In this chapter of the manual we will cover the usage of files and directories +by OpenMW CS. Files and directories are file system concepts of your operating +system, so we will not be going into specifics about that, we will only focus +on what is relevant to OpenMW CS. + + +Basics +****** + + +Directories +=========== + +OpenMW and OpenMW CS us multiple directories on the file system. First of all +there is a *user directory* that holds configuration files and a number of +different sub-directories. The location of the user directory is hard-coded +into the CS and depends on your operating system. + +================ ========================================= +Operating System User Dircetory +================ ========================================= +GNU/Linux ```` +OS X ``~/Library/Application Support/openmw/`` +Windows ```` +================ ========================================= + +In addition to to this single hard-coded directory both OpenMW and OpenMW CS +need a place to seek for a actuals data files of the game: textures, 3D models, +sounds and record files that store objects in game; dialogues an so one. These +files are called *content files*. We support multiple such paths (we call them +*data paths*) as specified in the configuration. Usually one data path points +to the directory where the original Morrowind game is either installed or +unpacked to. You are free to specify as many data paths as you would like, +however, there is one special data path that, as described later, which is used +to store newly created content files. + + +Content files +============= + +The original Morrowind engine by Bethesda Softworks uses two types of content +files: `esm` (master) and `esp` (plugin). The distinction between those two is +not clear, and often confusing. One would expect the `esm` (master) file to be +used to specify one master, which is then modified by the `esp` plugins. And +indeed: this is the basic idea. However, the official expansions were also made +as ESM files, even though they could essentially be described as really large +plugins, and therefore would rather use `esp` files. There were technical +reasons behind this decision – somewhat valid in the case of the original +engine, but clearly it is better to create a system that can be used in a more +sensible way. OpenMW achieves this with our own content file types. + +We support both ESM and ESP files, but in order to make use of new features in +OpenMW one should consider using new file types designed with our engine in +mind: *game* files and *addon* files, collectively called *content files*. + + +OpenMW content files +-------------------- + +The concepts of *Game* and *Addon* files are somewhat similar to the old +concept of *ESM* and *ESP*, but more strictly enforced. It is quite +straight-formward: If you want to make new game using OpenMW as the engine (a +so called *total conversion*) you should create a game file. If you want to +create an addon for an existing game file create an addon file. Nothing else +matters; the only distinction you should consider is if your project is about +changing another game or creating a new one. Simple as that. + +Another simple thing about content files are the extensions: we are using +``.omwaddon`` for addon files and ``.omwgame`` for game files. + + +Morrowind content files +----------------------- + +Using our content files is recommended for projects that are intended to used +with the OpenMW engine. However, some players might wish to still use the +original Morrowind engine. In addition thousands of *ESP*/*ESM* files were +created since 2002, some of them with really outstanding content. Because of +this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If +you decid to choose *ESP*/*ESM* file instead of using our own content file +types you are most likely aimng at compatibility with the original engine. This +subject is covered in it own chapter of this manual. + + +.. TODO This paragraph sounds weird + +The actual creation of new files is described in the next chapter. Here we are +going to focus only on the details you need to know in order to create your +first OpenMW CS file while fully understanding your needs. For now let’s jut +remember that content files are created inside the user directory in the the +``data`` subdirectory (that is the one special data directory mentioned +earlier). + + +Dependencies +------------ + +Since an addon is supposed to change the game it follows that it also depends +on the said game to work. We can conceptualise this with an examples: your +modification is the changing prize of an iron sword, but what if there is no +iron sword in game? That's right: we get nonsense. What you want to do is tie +your addon to the files you are changing. Those can be either game files (for +example when making an expansion island for a game) or other addon files +(making a house on said island). Obviously It is a good idea to be dependent +only on files that are really changed in your addon, but sadly there is no +other way to achieve this than knowing what you want to do. Again, please +remember that this section of the manual does not cover creating the content +files – it is only a theoretical introduction to the subject. For now just keep +in mind that dependencies exist, and is up to you to decide whether your +content file should depend on other content files. + +Game files are not intend to have any dependencies for a very simple reasons: +the player is using only one game file (excluding original and the dirty +ESP/ESM system) at a time and therefore no game file can depend on other game +file, and since a game file makes the base for addon files it can not depend on +addon files. + + +Project files +------------- + +Project files act as containers for data not used by the OpenMW game engine +itself, but still useful for OpenMW CS. The shining example of this data +category are without doubt record filters (described in a later chapter of the +manual). As a mod author you probably do not need or want to distribute project +files at all, they are meant to be used only by you and your team. + +.. TODO "you and your team": is that correct? + +As you would imagine, project files make sense only in combination with actual +content files. In fact, each time you start to work on new content file and a +project file was not found, one will be created. The extensio of project files +is ``.project``. The whole name of the project file is the whole name of the +content file with appended extension. For instance a ``swords.omwaddon`` file +is associated with a ``swords.omwaddon.project`` file. + +Project files are stored inside the user directory, in the ``projects`` +subdirectory. This is the path location for both freshly created project files, +and a place where OpenMW CS looks for already existing files. + + +Resource files +============== + +.. 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 +textures, images acting as icons, sounds and anything else. Since content +files, no matter whether they are *ESP*, *ESM* or new OpenMW file type, do not +contain any of those, it is clear that they have to be delivered with a +different file. It is also clear that this, let’s call it “resources file“, +has to be supported by the engine. Without code handling those files it is +nothing more than a mathematical abstraction – something, that lacks meaning +for human beings. Therefore this section must cover ways to add resources +files to your content file, and point out what is supported. We are going to do +just that. Later, you will learn how to make use of those files in your +content. + + +Audio +----- + +OpenMW uses FFmpeg_ for audio playback, and so we support every audio type +supported by that library. This makes a huge list. Below is only small portion +of the supported file types. + +mp3 (MPEG-1 Part 3 Layer 3) + A popular audio file format and de facto standard for storing audio. Used by + the Morrowind game. + +ogg + An open source, multimedia container file using a high quality Vorbis_ audio + codec. Recommended. + + +Video +----- + +Video As in the case of audio files, we are using FFmepg to decode video files. +The list of supported files is long, we will cover only the most significant. + +bik + Videos used by the original Morrowind game. + +mp4 + A multimedia container which use more advanced codecs (MPEG-4 Parts 2, 3 and + 10) with a better audio and video compression rate, but also requiring more + CPU intensive decoding – this makes it probably less suited for storing + sounds in computer games, but good for videos. + +webm + A new, shiny and open source video format with excellent compression. It + needs quite a lot of processing power to be decoded, but since game logic is + not running during cutscenes we can recommend it for use with OpenMW. + +ogv + Alternative, open source container using Theora_ codec for video and Vorbis for audio. + + + +Textures and images +------------------- + +The original Morrowind game uses *DDS* and *TGA* files for all kinds of two +dimensional images and textures alike. In addition, the engine supported *BMP* +files for some reason (*BMP* is a terrible format for a video game). We also +support an extended set of image files – including *JPEG* and *PNG*. *JPEG* and +*PNG* files can be useful in some cases, for instance a *JPEG* file is a valid +option for skybox texture and *PNG* can useful for masks. However, please keep +in mind that *JPEG* images can grow to large sizes quickly and are not the best +option with a DirectX rendering backend. You probably still want to use *DDS* +files for textures. + + + +.. Hyperlink targets for the entire document + +.. _FFmpeg: http://ffmpeg.org +.. _Vorbis: http://www.vorbis.com +.. _Theora: http://www.theora.org diff --git a/docs/cs-manual/source/foreword.rst b/docs/cs-manual/source/foreword.rst new file mode 100644 index 0000000000..613eca5e34 --- /dev/null +++ b/docs/cs-manual/source/foreword.rst @@ -0,0 +1,21 @@ +Foreword +######## + + + + +How to read the manual +********************** + +The manual can be roughly divided into two parts: a tutorial part consisting of +the first two (three) chapters and the reference manual. We recommend all +readers to work through the tutorials first, there you will be guided through +the creation of a fairly simple mod where you can familiarise yourself with the +record-based interface. The tutorials are very simple and teach you only what +is necessary for the task, each one can be completed in under half an hour. It +is strongly recommended to do the tutorials in order. + +When you are familiar with the CS in general and want to write your own mods it +is time to move on to the reference part of the manual. The reference chapters +can be read out of order, each one covers only one topic. + diff --git a/docs/cs-manual/source/index.rst b/docs/cs-manual/source/index.rst new file mode 100644 index 0000000000..ce50b8c95a --- /dev/null +++ b/docs/cs-manual/source/index.rst @@ -0,0 +1,30 @@ +.. OpenMW CS Manual documentation master file, created by + sphinx-quickstart on Fri Feb 5 21:28:27 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +##################### +OpenMW CS user manual +##################### + +The following document is the complete user manual for *OpenMW CS*, the +construction set for the OpenMW game engine. It is intended to serve both as an +introduction and a reference for the application. Even if you are familiar with +modding *The Elder Scrolls III: Morrowind* you should at least read the first +few chapters to familiarise yourself with the new interface. + +.. warning:: + OpenMW CS is still software in development. The manual does not cover any of + its shortcomings, it is written as if everything was working as inteded. + Please report any software problems as bugs in the software, not errors in + the manual. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 2 + + foreword + tour + files-and-directories + starting-dialog + diff --git a/docs/cs-manual/source/starting-dialog.rst b/docs/cs-manual/source/starting-dialog.rst new file mode 100644 index 0000000000..02a65ff213 --- /dev/null +++ b/docs/cs-manual/source/starting-dialog.rst @@ -0,0 +1,40 @@ +OpenMW CS Starting Dialog +######################### + +In this chapter we will cover starting up OpenMW CS and the starting interface. +Start the CS the way intended for your operating system and you will be +presented with window and three main buttons and a small button with a +wrench-icon. The wrench will open the configuration dialog which we will cover +later. The three main buttons are the following: + +Create A New Game + Choose this option if you want to create an original game that does not + depend on any other content files. The distinction between game and addon in + the original Morrowind engine was somewhat blurry, but OpenMW is very strict + about it: regardless of how large your addon is, if it depends on another + content file it is not an original game. + +Create A New Addon + Choose this option if you want to create an extension to an existing game. + An addon can depend on other addons as well optionally, but it *must* depend + on a game. + +Edit A Content File + Choose this option is you wish to edit an existing content file, regardless + of whether it is a game or an addon. + +Whether you create a game or an addon, a data file and a project file will be +generated for you in you user directory. + +You will have to choose a name for your content file and if you chose to create +an addon you will also have to chose a number of dependencies. You have to +choose exactly one game and you can choose an arbitrary amount of addon +dependencies. For the sake of simplicity and maintainability choose only the +addons you actually want to depend on. Also keep in mind that your dependencies +might have dependencies of their own, you have to depend on those as well. If +one of your dependencies nees something it will be indicated by a warning sign +and automatically include its dependencies when you choose it. + +If you want to edit an existing content file you will be presented with a +similar dialog, except you don't get to choose a file name (because you are +editing files that already exist). diff --git a/docs/cs-manual/source/tour.rst b/docs/cs-manual/source/tour.rst new file mode 100644 index 0000000000..0e92fe6faa --- /dev/null +++ b/docs/cs-manual/source/tour.rst @@ -0,0 +1,223 @@ +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 don't need prior knowledge about modding +Morrowind, but you should be familiar with the game itself. There will be no +scripting necessary, we chan achieve everything using just what the base game +offers out of the box. Before continuing make sure that OpenMW is properly +installed and playable. + + +Adding the ring to the game's records +************************************* + +In this first section we will define what our new ring is, what it looks like +and what it does. Getting it to work is the first step before we go further. + + +Starting up OpenMW CS +===================== + +We will start by launching OpenMW CS, the location of the program depends on +your operating system. You will be presented with a dialogue with three +options: create a new game, create a new addon, edit a content file. + +.. figure:: ./_static/images/chapter-1/opening-dialogue.png + :alt: Opening dialogue with three option and setting button (the wrench) + +The first option is for creating an entirely new game, that's not what we want. +We want to edit an existing game, so choose the second one. When you save your +addon you can use the third option to open it again. + +You will be presented with another window where you get to chose the content to +edit and the name of your project. We have to chose at least a base game, and +optionally a number of other addons we want to depend on. The name of the +project is arbitrary, it will be used to identify the addon later in the OpenMW +launcher. + +.. figure:: ./_static/images/chapter-1/new-project.png + :alt: Creation dialogue for a new project, pick content modules and name + +Choose Morrowind as your content file and enter `Ring of Night Vision` as the +name. We could also chose further content files as dependencies if we wanted +to, but for this mod the base game is enough. + +Once the addon has been created you will be presented with a table. If you see +a blank window rather than a table choose *World* → *Objects* from the menu. + +.. figure:: ./_static/images/chapter-1/objects.png + :alt: The table showing all objet records in the game. + +Let's talk about the interface for a second. Every window in OpenMW CS has +*panels*, these are often but not always tables. You can close a panel by +clicking the small "X" on the title bar of the panel, or you can detach it by +either dragging the title bar or clicking the icon with the two windows. A +detached panel can be re-attached to a window by dragging it by the title bar +on top of the window. + +Now let's look at the panel itself: we have a filter text field, a very large +table and a status bar. The filter will be very useful when we want to find an +entry in the table, but for now it is irrelevant. The table you are looking at +contains all objects in the game, these can be items, NPCs, creatures, +whatever. Every object is an entry in that table, visible as a row. The columns +of the table are the attributes of each object. + +Morrowind uses something called a *relational database* for game data. If you +are not familiar with the term, it means that every type of thing can be +expressed as a *table*: there is a table for objects, a table for enchantments, +a table for icons, one for meshes, and so on. Properties of an entry must be +simple values, like numbers or text strings. If we want a more complicated +property we need to reference an entry from another table. There are a few +exceptions to this though, some tables do have subtables. The effects of +enchantments are one of those exceptions. + + +Defining a new record +===================== + +Enough talk, let's create the new ring now. Right-click anywhere in the objects +table, choose `Add Record` and the status bar will change into an input field. +We need to enter an *ID* (short for *identifier*) and pick the type. The +identifier is a unique name by which the ring can later be identified; I have +chosen `ring_night_vision`. For the type choose *Clothing*. + +.. figure:: ./_static/images/chapter-1/add-record.png + :alt: Enter the ID and type of the new ring + +The table should jump right to our newly created record, if not read further +below how to use filters to find a record by ID. Notice that the *Modified* +column now shows that this record is new. Records can also be *Base* +(unmodified), *Modified* and *Deleted*. The other fields are still empty since +we created this record from nothing. We can double-click a table cell while +holding Shift to edit it (this is a configurable shortcut), but there is a +better way: right-click the row of our new record and chose *Edit Record*, a +new panel will open. + +We can right-click the row of our new record and chose *Edit Record*, a +new panel will open. Alternatively we can also define a configurable shortcut +instead of using the context menu; the default is double-clicking while +holding down the shift key. + + +.. figure:: ./_static/images/chapter-1/edit-record.png + :alt: Edit the properties of the record in a separate panel + +You can set the name, weight and coin value as you like, I chose `Ring of Night +Vision`, `0.1` and `2500` respectively. Make sure you set the *Clothing Type* +to *Ring*. We could set the other properties manually as well, but unless you +have an exceptional memory for identifiers and never make typos that's not +feasible. What we are going to do instead is find the records we want in their +respective tables and assign them from there. + + +Finding records using filters +============================= + +We will add an icon first. Open the *Icons* table the same way you opened the +*Objects* table: in the menu click *Assets* → *Icons*. If the window gets too +crowded remember that you can detach panels. The table is huge and not every +ring icon starts with "ring", so we have to use filters to find what we want. + +Filters are a central element of OpenMW CS and a major departure from how the +original Morrowind CS was used. In fact, filters are so important that they +have their own table as well. We won't be going that far for now though. There +are three types of filters: *Project filters* are part of the project and are +stored in the project file, *session filter* are only valid until you exit the +CS, and finally *instant filter* which are used only once and typed directly +into the *Filter* field. + +For this tutorial we will only use instant filters. We type the definition of +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:: + + !string("id", ".*ring.*") + +A filter is defined by a number of *queries* which can be logically linked. For +now all that matters is that the `string(, )` query will check +whether `` matches ``. The pattern is a regular expression, +if you don't know about them you should learn their syntax. For now all that +matters is that `.` stands for any character and `*` stands for any amount, +even zero. In other words, we are looking for all entries which have an ID that +contains the word "ring" somewhere in it. This is a pretty dumb pattern because +it will also match words like "ringmail", but it's good enough for now. + +If you have typed the filter definition properly the text should change from +red to black and our table will be narrowed down a lot. Browse for an icon you +like and drag & drop its table row onto the *Icon* field of our new ring. + +That's it, you have now assigned a reference to an entry in another table to +the ring entry in the *Objects* table. Repeat the same process for the 3D +model, you can find the *Meshes* table under *Assets* → *Meshes*. + + +Adding the enchantment +====================== + +Putting everything you have learned so far to practice we can add the final and +most important part to our new ring: the enchantment. You know enough to +perform the following steps without guidance: Open the *Enchantments* table +(*Mechanics* → *Enchantments*) and create a new entry with the ID `Cats Eye`. +Edit it so that it has *Constant Effect* enchantment type. + +To add an effect to the enchantment right-click the *Magic Effects* table and +choose *Add new row*. You can edit the effects by right-clicking their table +cells. Set the effect to *NightEye*, range to *Self*, and both magnitudes to +`50`. The other properties are irrelevant. + +Once you are done add the new enchantment to our ring. That's it, we now have a +complete enchanted ring to play with. Let's take it for a test ride. + + +Playing your new addon +====================== + +Launch OpenMW and in the launcher under *Data Files* check your addon. Load a +game and open the console. We have only defined the ring, but we haven't placed +any instance of it anywhere in the game world, so we have to create one. In the +console type: + +.. code:: + + player->AddItem "ring_night_vision" 1 + +The part in quotation marks is the ID of our ring, you have to adjust it if you +chose a different ID. Exit the console and you should find a new ring in your +inventory. Equip it and you will instantly receive the *Night Vision* effect +for your character. + + +Conclusion +========== + +In this tutorial we have learned how to create a new addon, what tables are and +how to create new records. We have also taken a very brief glimpse at the +syntax of filters, a feature you will be using a lot when creating larger mods. + +This mod is a pure addition, it does not change any of the existing records. +However, if you want to actually present appealing content to the player rather +than just offering abstract definitions you will have to change the game's +content. In the next tutorial we will learn how to place the ring in the game +world so the player can find it legitimately. + + + +Adding the ring to the game's world +*********************************** + +Now that we have defined the ring it is time add it to the game world so the +player can find it legitimately. We will add the ring to a merchant, place it +in a chest and put it somewhere in plain sight. To this end we will have to +actually modify the contents of the game. + + +Subsection to come... +===================== + + + + From e02f35264fd2e8f78d258d73cb5f6c709b110e64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Feb 2016 15:08:12 +0100 Subject: [PATCH 192/765] Work around OSG 3.2 not respecting the DEEP_COPY_CALLBACK flag (Fixes #3183) --- components/sceneutil/clone.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 26f3f5c7cd..784195e7ef 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -81,6 +81,11 @@ namespace SceneUtil #endif osg::Drawable* cloned = osg::clone(drawable, copyop); +#if OSG_VERSION_LESS_THAN(3,3,3) + // work around OSG 3.2 not respecting the DEEP_COPY_CALLBACK flag + if (cloned->getUpdateCallback()) + cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); +#endif return cloned; } if (dynamic_cast(drawable)) From df57d4bfba49b41b692c7db85b9ef774fa772570 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Feb 2016 16:57:54 +0100 Subject: [PATCH 193/765] Use a common base class for resource managers Implement updateCache to delete unreferenced cached objects when they have not been referenced for a while. --- apps/openmw/mwphysics/physicssystem.cpp | 5 +++ apps/openmw/mwphysics/physicssystem.hpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/CMakeLists.txt | 2 +- components/resource/bulletshapemanager.cpp | 3 +- components/resource/bulletshapemanager.hpp | 16 +------- components/resource/imagemanager.cpp | 3 +- components/resource/imagemanager.hpp | 16 ++------ components/resource/keyframemanager.cpp | 3 +- components/resource/keyframemanager.hpp | 19 +--------- components/resource/niffilemanager.cpp | 10 +---- components/resource/niffilemanager.hpp | 20 +--------- components/resource/resourcemanager.cpp | 34 +++++++++++++++++ components/resource/resourcemanager.hpp | 43 ++++++++++++++++++++++ components/resource/resourcesystem.cpp | 29 ++++++++++++++- components/resource/resourcesystem.hpp | 17 ++++++++- components/resource/scenemanager.cpp | 9 +---- components/resource/scenemanager.hpp | 19 ++-------- components/vfs/manager.hpp | 6 +++ 19 files changed, 152 insertions(+), 105 deletions(-) create mode 100644 components/resource/resourcemanager.cpp create mode 100644 components/resource/resourcemanager.hpp diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c99456ae52..11d6d286a4 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -640,12 +640,15 @@ namespace MWPhysics PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) + , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) { + mResourceSystem->addResourceManager(mShapeManager.get()); + mCollisionConfiguration = new btDefaultCollisionConfiguration(); mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); mBroadphase = new btDbvtBroadphase(); @@ -659,6 +662,8 @@ namespace MWPhysics PhysicsSystem::~PhysicsSystem() { + mResourceSystem->removeResourceManager(mShapeManager.get()); + if (mWaterCollisionObject.get()) mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index f53d7e3d9b..4263bc0ec3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -169,6 +169,7 @@ namespace MWPhysics btCollisionWorld* mCollisionWorld; std::auto_ptr mShapeManager; + Resource::ResourceSystem* mResourceSystem; typedef std::map ObjectMap; ObjectMap mObjects; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9b7ced4b58..d4199d5f6c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -233,7 +233,7 @@ namespace MWRender void RenderingManager::clearCache() { - mResourceSystem->clearCache(); + mResourceSystem->updateCache(mViewer->getFrameStamp()->getReferenceTime()); if (mTerrain.get()) mTerrain->clearCache(); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0b54ddeb73..42323a68a0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,7 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache resourcesystem resourcemanager ) add_component_dir (sceneutil diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 2ab7b243aa..b5581cce24 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -97,10 +97,9 @@ private: }; BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) - : mVFS(vfs) + : ResourceManager(vfs) , mSceneManager(sceneMgr) , mNifFileManager(nifFileManager) - , mCache(new osgDB::ObjectCache) { } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index c8db8849e7..576268f753 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -7,16 +7,7 @@ #include #include "bulletshape.hpp" - -namespace VFS -{ - class Manager; -} - -namespace osgDB -{ - class ObjectCache; -} +#include "resourcemanager.hpp" namespace Resource { @@ -29,7 +20,7 @@ namespace Resource /// Handles loading, caching and "instancing" of bullet shapes. /// A shape 'instance' is a clone of another shape, with the goal of setting a different scale on this instance. /// @note May be used from any thread. - class BulletShapeManager + class BulletShapeManager : public ResourceManager { public: BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); @@ -38,11 +29,8 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); private: - const VFS::Manager* mVFS; SceneManager* mSceneManager; NifFileManager* mNifFileManager; - - osg::ref_ptr mCache; }; } diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index bd422fe2a6..79da7d7abf 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -42,8 +42,7 @@ namespace Resource { ImageManager::ImageManager(const VFS::Manager *vfs) - : mVFS(vfs) - , mCache(new osgDB::ObjectCache) + : ResourceManager(vfs) , mWarningImage(createWarningImage()) , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) { diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index bc1d7b4e4b..8d9ad2c327 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -8,20 +8,16 @@ #include #include +#include "resourcemanager.hpp" + namespace osgViewer { class Viewer; } -namespace VFS -{ - class Manager; -} - namespace osgDB { class Options; - class ObjectCache; } namespace Resource @@ -29,7 +25,7 @@ namespace Resource /// @brief Handles loading/caching of Images. /// @note May be used from any thread. - class ImageManager + class ImageManager : public ResourceManager { public: ImageManager(const VFS::Manager* vfs); @@ -39,15 +35,9 @@ namespace Resource /// Returns the dummy image if the given image is not found. osg::ref_ptr getImage(const std::string& filename); - const VFS::Manager* getVFS() { return mVFS; } - osg::Image* getWarningImage(); private: - const VFS::Manager* mVFS; - - osg::ref_ptr mCache; - osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 7e948dcb03..4392f84c19 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,8 +9,7 @@ namespace Resource { KeyframeManager::KeyframeManager(const VFS::Manager* vfs) - : mCache(new osgDB::ObjectCache) - , mVFS(vfs) + : ResourceManager(vfs) { } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 5a5cb36284..1c2c219bb1 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -4,15 +4,7 @@ #include #include -namespace VFS -{ - class Manager; -} - -namespace osgDB -{ - class ObjectCache; -} +#include "resourcemanager.hpp" namespace NifOsg { @@ -24,22 +16,15 @@ namespace Resource /// @brief Managing of keyframe resources /// @note May be used from any thread. - class KeyframeManager + class KeyframeManager : public ResourceManager { public: KeyframeManager(const VFS::Manager* vfs); ~KeyframeManager(); - void clearCache(); - /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); - - private: - osg::ref_ptr mCache; - - const VFS::Manager* mVFS; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 1d8019b69d..3c7437520b 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -29,9 +29,9 @@ namespace Resource }; NifFileManager::NifFileManager(const VFS::Manager *vfs) - : mVFS(vfs) + : ResourceManager(vfs, 0.0) // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, + // so we'll use expiryDelay of 0 to instantly delete NIF files after use. { - mCache = new osgDB::ObjectCache; } NifFileManager::~NifFileManager() @@ -39,12 +39,6 @@ namespace Resource } - void NifFileManager::clearCache() - { - // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, - // so we'll simply drop all nif files here, unlikely to need them again - mCache->clear(); - } Nif::NIFFilePtr NifFileManager::get(const std::string &name) { diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 4551cf2275..4b43ff24bf 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -5,39 +5,23 @@ #include -namespace VFS -{ - class Manager; -} - -namespace osgDB -{ - class ObjectCache; -} +#include "resourcemanager.hpp" namespace Resource { /// @brief Handles caching of NIFFiles. /// @note May be used from any thread. - class NifFileManager + class NifFileManager : public ResourceManager { public: NifFileManager(const VFS::Manager* vfs); ~NifFileManager(); - void clearCache(); - /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. Nif::NIFFilePtr get(const std::string& name); - - private: - // Use the osgDB::ObjectCache so objects are retrieved in thread safe way - osg::ref_ptr mCache; - - const VFS::Manager* mVFS; }; } diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp new file mode 100644 index 0000000000..60233baa0d --- /dev/null +++ b/components/resource/resourcemanager.cpp @@ -0,0 +1,34 @@ +#include "resourcemanager.hpp" + +#include "objectcache.hpp" + +namespace Resource +{ + + ResourceManager::ResourceManager(const VFS::Manager *vfs, const double expiryDelay) + : mVFS(vfs) + , mCache(new osgDB::ObjectCache) + , mExpiryDelay(expiryDelay) + { + + } + + void ResourceManager::updateCache(double referenceTime) + { + // NOTE: we could clear the cache from the background thread if the deletion proves too much of an overhead + // idea: customize objectCache to not hold a lock while doing the actual deletion + mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); + } + + void ResourceManager::clearCache() + { + mCache->clear(); + } + + const VFS::Manager* ResourceManager::getVFS() const + { + return mVFS; + } + +} diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp new file mode 100644 index 0000000000..e45a2e9cb7 --- /dev/null +++ b/components/resource/resourcemanager.hpp @@ -0,0 +1,43 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_MANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_MANAGER_H + +#include + +namespace VFS +{ + class Manager; +} + +namespace osgDB +{ + class ObjectCache; +} + +namespace Resource +{ + + /// @brief Base class for managers that require a virtual file system and object cache. + /// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes. + class ResourceManager + { + public: + /// @param expiryDelay how long to keep objects in cache after no longer being referenced. + ResourceManager(const VFS::Manager* vfs, const double expiryDelay = 300.0); + + /// Clear cache entries that have not been referenced for longer than expiryDelay. + virtual void updateCache(double referenceTime); + + /// Clear all cache entries regardless of having external references. + virtual void clearCache(); + + const VFS::Manager* getVFS() const; + + protected: + const VFS::Manager* mVFS; + osg::ref_ptr mCache; + double mExpiryDelay; + }; + +} + +#endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index d39a723d6b..f499c0016b 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -15,11 +15,21 @@ namespace Resource mKeyframeManager.reset(new KeyframeManager(vfs)); mImageManager.reset(new ImageManager(vfs)); mSceneManager.reset(new SceneManager(vfs, mImageManager.get(), mNifFileManager.get())); + + addResourceManager(mNifFileManager.get()); + addResourceManager(mKeyframeManager.get()); + // note, scene references images so add images afterwards for correct implementation of updateCache() + addResourceManager(mSceneManager.get()); + addResourceManager(mImageManager.get()); } ResourceSystem::~ResourceSystem() { // this has to be defined in the .cpp file as we can't delete incomplete types + + mResourceManagers.clear(); + + // no delete, all handled by auto_ptr } SceneManager* ResourceSystem::getSceneManager() @@ -42,9 +52,24 @@ namespace Resource return mKeyframeManager.get(); } - void ResourceSystem::clearCache() + void ResourceSystem::updateCache(double referenceTime) { - mNifFileManager->clearCache(); + osg::Timer timer; + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->updateCache(referenceTime); + std::cout << "updateCache took " << timer.time_m() << " ms" << std::endl; + } + + void ResourceSystem::addResourceManager(ResourceManager *resourceMgr) + { + mResourceManagers.push_back(resourceMgr); + } + + void ResourceSystem::removeResourceManager(ResourceManager *resourceMgr) + { + std::vector::iterator found = std::find(mResourceManagers.begin(), mResourceManagers.end(), resourceMgr); + if (found != mResourceManagers.end()) + mResourceManagers.erase(found); } const VFS::Manager* ResourceSystem::getVFS() const diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 13a96e8c7f..30607432da 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H #include +#include namespace VFS { @@ -15,6 +16,7 @@ namespace Resource class ImageManager; class NifFileManager; class KeyframeManager; + class ResourceManager; /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but @@ -31,8 +33,17 @@ namespace Resource KeyframeManager* getKeyframeManager(); /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer referenced. - void clearCache(); + /// @note May be called from any thread if you do not add or remove resource managers at that point. + void updateCache(double referenceTime); + /// Add this ResourceManager to be handled by the ResourceSystem. + /// @note Does not transfer ownership. + void addResourceManager(ResourceManager* resourceMgr); + /// @note Do nothing if resourceMgr does not exist. + /// @note Does not delete resourceMgr. + void removeResourceManager(ResourceManager* resourceMgr); + + /// @note May be called from any thread. const VFS::Manager* getVFS() const; private: @@ -41,6 +52,10 @@ namespace Resource std::auto_ptr mNifFileManager; std::auto_ptr mKeyframeManager; + // Store the base classes separately to get convenient access to the common interface + // Here users can register their own resourcemanager as well + std::vector mResourceManagers; + const VFS::Manager* mVFS; ResourceSystem(const ResourceSystem&); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 95e03b3894..4ed14241a2 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -230,7 +230,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) - : mVFS(vfs) + : ResourceManager(vfs) , mImageManager(imageManager) , mNifFileManager(nifFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) @@ -238,7 +238,6 @@ namespace Resource , mMaxAnisotropy(1) , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) - , mCache(new osgDB::ObjectCache) { } @@ -403,11 +402,6 @@ namespace Resource node->accept(visitor); } - const VFS::Manager* SceneManager::getVFS() const - { - return mVFS; - } - Resource::ImageManager* SceneManager::getImageManager() { return mImageManager; @@ -483,5 +477,4 @@ namespace Resource mUnRefImageDataAfterApply = unref; } - } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 32bd806603..173131e666 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -8,27 +8,19 @@ #include #include +#include "resourcemanager.hpp" + namespace Resource { class ImageManager; class NifFileManager; } -namespace VFS -{ - class Manager; -} - namespace osgUtil { class IncrementalCompileOperation; } -namespace osgDB -{ - class ObjectCache; -} - namespace osgViewer { class Viewer; @@ -39,7 +31,7 @@ namespace Resource /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. - class SceneManager + class SceneManager : public ResourceManager { public: SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); @@ -78,8 +70,6 @@ namespace Resource /// @note SceneManager::attachTo calls this method automatically, only needs to be called by users if manually attaching void notifyAttached(osg::Node* node) const; - const VFS::Manager* getVFS() const; - Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. @@ -99,7 +89,6 @@ namespace Resource void setUnRefImageDataAfterApply(bool unref); private: - const VFS::Manager* mVFS; Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; @@ -112,8 +101,6 @@ namespace Resource unsigned int mParticleSystemMask; - osg::ref_ptr mCache; - SceneManager(const SceneManager&); void operator = (const SceneManager&); }; diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index f74914977a..6592a65a88 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -16,6 +16,7 @@ namespace VFS /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is /// contained in multiple archives, the last added archive will have priority. + /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { public: @@ -33,20 +34,25 @@ namespace VFS void buildIndex(); /// Does a file with this name exist? + /// @note May be called from any thread once the index has been built. bool exists(const std::string& name) const; /// Get a complete list of files from all archives + /// @note May be called from any thread once the index has been built. const std::map& getIndex() const; /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. + /// @note May be called from any thread once the index has been built. void normalizeFilename(std::string& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(const std::string& name) const; /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; private: From b7e69cbc64fafd1f8ae255c4425f0e3b5cdcdd6e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Feb 2016 22:29:06 +0100 Subject: [PATCH 194/765] Refactor WorkQueue, merge WorkTicket and WorkItem Allow the caller to hold on to the WorkItem. This makes it possible for a derived WorkItem to store the result of the work within the WorkItem itself. --- components/sceneutil/workqueue.cpp | 39 ++++++------- components/sceneutil/workqueue.hpp | 89 ++++++++++++++---------------- 2 files changed, 59 insertions(+), 69 deletions(-) diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 26a392be45..bb1d1f1f70 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -1,9 +1,11 @@ #include "workqueue.hpp" +#include + namespace SceneUtil { -void WorkTicket::waitTillDone() +void WorkItem::waitTillDone() { if (mDone > 0) return; @@ -15,7 +17,7 @@ void WorkTicket::waitTillDone() } } -void WorkTicket::signalDone() +void WorkItem::signalDone() { { OpenThreads::ScopedLock lock(mMutex); @@ -25,23 +27,16 @@ void WorkTicket::signalDone() } WorkItem::WorkItem() - : mTicket(new WorkTicket) { - mTicket->setThreadSafeRefUnref(true); } WorkItem::~WorkItem() { } -void WorkItem::doWork() +bool WorkItem::isDone() const { - mTicket->signalDone(); -} - -osg::ref_ptr WorkItem::getTicket() -{ - return mTicket; + return (mDone > 0); } WorkQueue::WorkQueue(int workerThreads) @@ -60,11 +55,7 @@ WorkQueue::~WorkQueue() { OpenThreads::ScopedLock lock(mMutex); while (!mQueue.empty()) - { - WorkItem* item = mQueue.front(); - delete item; mQueue.pop(); - } mIsReleased = true; mCondition.broadcast(); } @@ -76,16 +67,20 @@ WorkQueue::~WorkQueue() } } -osg::ref_ptr WorkQueue::addWorkItem(WorkItem *item) +void WorkQueue::addWorkItem(osg::ref_ptr item) { - osg::ref_ptr ticket = item->getTicket(); + if (item->isDone()) + { + std::cerr << "warning, trying to add a work item that is already completed" << std::endl; + return; + } + OpenThreads::ScopedLock lock(mMutex); mQueue.push(item); mCondition.signal(); - return ticket; } -WorkItem *WorkQueue::removeWorkItem() +osg::ref_ptr WorkQueue::removeWorkItem() { OpenThreads::ScopedLock lock(mMutex); while (mQueue.empty() && !mIsReleased) @@ -94,7 +89,7 @@ WorkItem *WorkQueue::removeWorkItem() } if (mQueue.size()) { - WorkItem* item = mQueue.front(); + osg::ref_ptr item = mQueue.front(); mQueue.pop(); return item; } @@ -111,11 +106,11 @@ void WorkThread::run() { while (true) { - WorkItem* item = mWorkQueue->removeWorkItem(); + osg::ref_ptr item = mWorkQueue->removeWorkItem(); if (!item) return; item->doWork(); - delete item; + item->signalDone(); } } diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 492bbd090e..06489dfbbc 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -14,37 +14,61 @@ namespace SceneUtil { - class WorkTicket : public osg::Referenced - { - public: - void waitTillDone(); - - void signalDone(); - - private: - OpenThreads::Atomic mDone; - OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; - }; - - class WorkItem + class WorkItem : public osg::Referenced { public: WorkItem(); virtual ~WorkItem(); /// Override in a derived WorkItem to perform actual work. - /// By default, just signals the ticket that the work is done. - virtual void doWork(); + virtual void doWork() {} - osg::ref_ptr getTicket(); + bool isDone() const; + + /// Wait until the work is completed. Usually called from the main thread. + void waitTillDone(); + + /// Internal use by the WorkQueue. + void signalDone(); protected: - osg::ref_ptr mTicket; + OpenThreads::Atomic mDone; + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; }; class WorkQueue; + class WorkThread; + /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. + /// @note Work items will be processed in the order that they were given in, however + /// if multiple work threads are involved then it is possible for a later item to complete before earlier items. + class WorkQueue : public osg::Referenced + { + public: + WorkQueue(int numWorkerThreads=1); + ~WorkQueue(); + + /// Add a new work item to the back of the queue. + /// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete. + void addWorkItem(osg::ref_ptr item); + + /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. + /// If the workqueue is in the process of being destroyed, may return NULL. + /// @par Used internally by the WorkThread. + osg::ref_ptr removeWorkItem(); + + private: + bool mIsReleased; + std::queue > mQueue; + + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; + + std::vector mThreads; + }; + + /// Internally used by WorkQueue. class WorkThread : public OpenThreads::Thread { public: @@ -56,35 +80,6 @@ namespace SceneUtil WorkQueue* mWorkQueue; }; - /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. - class WorkQueue - { - public: - WorkQueue(int numWorkerThreads=1); - ~WorkQueue(); - - /// Add a new work item to the back of the queue. - /// @par The returned WorkTicket may be used by the caller to wait until the work is complete. - osg::ref_ptr addWorkItem(WorkItem* item); - - /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. - /// If the workqueue is in the process of being destroyed, may return NULL. - /// @note The caller must free the returned WorkItem - WorkItem* removeWorkItem(); - - void runThread(); - - private: - bool mIsReleased; - std::queue mQueue; - - OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; - - std::vector mThreads; - }; - - } From e055ae094aeab055caecb04c3d898656c656406b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Feb 2016 23:30:41 +0100 Subject: [PATCH 195/765] Improve const-correctness in BulletShapeManager Sadly, two const_cast's are needed to work around Bullet API quirks. --- components/resource/bulletshape.cpp | 26 ++++++++++------------ components/resource/bulletshape.hpp | 8 +++---- components/resource/bulletshapemanager.cpp | 9 ++++++-- components/resource/bulletshapemanager.hpp | 3 +++ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index baff86a794..6855429c36 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -45,11 +45,11 @@ void BulletShape::deleteShape(btCollisionShape* shape) } } -btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const +btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *shape) const { if(shape->isCompound()) { - btCompoundShape *comp = static_cast(shape); + const btCompoundShape *comp = static_cast(shape); btCompoundShape *newShape = new btCompoundShape; int numShapes = comp->getNumChildShapes(); @@ -63,29 +63,27 @@ btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) return newShape; } - if(btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) { #if BT_BULLET_VERSION >= 283 - btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f)); + btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); #else // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions - btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); + const btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) - bool buildBvh = true; - if (trishape->getOptimizedBvh()) - buildBvh = false; - TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, buildBvh); + btOptimizedBvh* bvh = const_cast(trishape)->getOptimizedBvh(); + TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, bvh == NULL); // Set original shape's bvh via pointer // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape - if (!buildBvh) - newShape->setOptimizedBvh(trishape->getOptimizedBvh()); + if (bvh) + newShape->setOptimizedBvh(bvh); #endif return newShape; } - if (btBoxShape* boxshape = dynamic_cast(shape)) + if (const btBoxShape* boxshape = dynamic_cast(shape)) { return new btBoxShape(*boxshape); } @@ -98,13 +96,13 @@ btCollisionShape *BulletShape::getCollisionShape() return mCollisionShape; } -osg::ref_ptr BulletShape::makeInstance() +osg::ref_ptr BulletShape::makeInstance() const { osg::ref_ptr instance (new BulletShapeInstance(this)); return instance; } -BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) +BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : BulletShape() , mSource(source) { diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index a007bad310..a418bb28ce 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -38,9 +38,9 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; - osg::ref_ptr makeInstance(); + osg::ref_ptr makeInstance() const; - btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const; + btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const; btCollisionShape* getCollisionShape(); @@ -55,10 +55,10 @@ namespace Resource class BulletShapeInstance : public BulletShape { public: - BulletShapeInstance(osg::ref_ptr source); + BulletShapeInstance(osg::ref_ptr source); private: - osg::ref_ptr mSource; + osg::ref_ptr mSource; }; // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b5581cce24..cb57d686ff 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -109,7 +109,7 @@ BulletShapeManager::~BulletShapeManager() } -osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) +osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); @@ -142,13 +142,18 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: if (!shape) { mCache->addEntryToObjectCache(normalized, NULL); - return osg::ref_ptr(); + return osg::ref_ptr(); } } mCache->addEntryToObjectCache(normalized, shape); } + return shape; +} +osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) +{ + osg::ref_ptr shape = getShape(name); if (shape) return shape->makeInstance(); else diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 576268f753..4c0cb1fd29 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -26,6 +26,9 @@ namespace Resource BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); ~BulletShapeManager(); + osg::ref_ptr getShape(const std::string& name); + + /// Shorthand for getShape(name)->makeInstance(); osg::ref_ptr createInstance(const std::string& name); private: From 6f9ca0f68fd76bfd1abef2f9e5ae0131b8a64e89 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 00:07:02 +0100 Subject: [PATCH 196/765] Add basic cell preloader class Not properly in use yet, but seems to be working. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwphysics/physicssystem.cpp | 5 + apps/openmw/mwphysics/physicssystem.hpp | 2 + apps/openmw/mwworld/cellpreloader.cpp | 148 ++++++++++++++++++++++++ apps/openmw/mwworld/cellpreloader.hpp | 60 ++++++++++ apps/openmw/mwworld/cellstore.cpp | 5 + apps/openmw/mwworld/cellstore.hpp | 3 + components/CMakeLists.txt | 4 +- components/resource/resourcesystem.cpp | 3 + 9 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwworld/cellpreloader.cpp create mode 100644 apps/openmw/mwworld/cellpreloader.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 02cf6c87dd..f2540c7395 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -67,6 +67,7 @@ add_openmw_dir (mwworld actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + cellpreloader ) add_openmw_dir (mwphysics diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 11d6d286a4..6417968d3a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -690,6 +690,11 @@ namespace MWPhysics delete mBroadphase; } + Resource::BulletShapeManager *PhysicsSystem::getShapeManager() + { + return mShapeManager.get(); + } + bool PhysicsSystem::toggleDebugRendering() { mDebugDrawEnabled = !mDebugDrawEnabled; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4263bc0ec3..a866717c76 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -53,6 +53,8 @@ namespace MWPhysics PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); ~PhysicsSystem (); + Resource::BulletShapeManager* getShapeManager(); + void enableWater(float height); void setWaterHeight(float height); void disableWater(); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp new file mode 100644 index 0000000000..dd6850abae --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -0,0 +1,148 @@ +#include "cellpreloader.hpp" + +#include + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "cellstore.hpp" +#include "manualref.hpp" +#include "class.hpp" + +namespace MWWorld +{ + + struct ListModelsVisitor + { + ListModelsVisitor(std::vector& out) + : mOut(out) + { + } + + virtual bool operator()(const MWWorld::ConstPtr& ptr) + { + std::string model = ptr.getClass().getModel(ptr); + if (!model.empty()) + mOut.push_back(model); + return true; + } + + std::vector& mOut; + }; + + class PreloadItem : public SceneUtil::WorkItem + { + public: + /// Constructor to be called from the main thread. + PreloadItem(const MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager) + : mSceneManager(sceneManager) + , mBulletShapeManager(bulletShapeManager) + { + if (cell->getState() == MWWorld::CellStore::State_Loaded) + { + ListModelsVisitor visitor (mMeshes); + cell->forEachConst(visitor); + } + else + { + const std::vector& objectIds = cell->getPreloadedIds(); + + // could possibly build the model list in the worker thread if we manage to make the Store thread safe + for (std::vector::const_iterator it = objectIds.begin(); it != objectIds.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), *it); + std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + if (!model.empty()) + mMeshes.push_back(model); + } + } + } + + /// Preload work to be called from the worker thread. + virtual void doWork() + { + for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) + { + try + { + mPreloadedNodes.push_back(mSceneManager->getTemplate(*it)); + mPreloadedShapes.push_back(mBulletShapeManager->getShape(*it)); + + // TODO: do a createInstance() and hold on to it since we can make use of it when the cell goes active + } + catch (std::exception& e) + { + // ignore error for now, would spam the log too much + // error will be shown when visiting the cell + } + } + } + + private: + typedef std::vector MeshList; + MeshList mMeshes; + Resource::SceneManager* mSceneManager; + Resource::BulletShapeManager* mBulletShapeManager; + + // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state + std::vector > mPreloadedNodes; + std::vector > mPreloadedShapes; + }; + + CellPreloader::CellPreloader(Resource::SceneManager *sceneManager, Resource::BulletShapeManager* bulletShapeManager) + : mSceneManager(sceneManager) + , mBulletShapeManager(bulletShapeManager) + { + mWorkQueue = new SceneUtil::WorkQueue; + } + + void CellPreloader::preload(const CellStore *cell, double timestamp) + { + if (!mWorkQueue) + { + std::cerr << "can't preload, no work queue set " << std::endl; + return; + } + if (cell->getState() == CellStore::State_Unloaded) + { + std::cerr << "can't preload objects for unloaded cell" << std::endl; + return; + } + + PreloadMap::iterator found = mPreloadCells.find(cell); + if (found != mPreloadCells.end()) + { + // already preloaded, nothing to do other than updating the timestamp + found->second.mTimeStamp = timestamp; + return; + } + + osg::ref_ptr item (new PreloadItem(cell, mSceneManager, mBulletShapeManager)); + mWorkQueue->addWorkItem(item); + + mPreloadCells[cell] = PreloadEntry(timestamp, item); + } + + void CellPreloader::updateCache(double timestamp) + { + // time (in seconds) to keep a preloaded cell in cache after it's no longer needed + const double expiryTime = 60.0; + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) + { + if (it->second.mTimeStamp < timestamp - expiryTime) + mPreloadCells.erase(it++); + else + ++it; + } + } + + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) + { + mWorkQueue = workQueue; + } + +} diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp new file mode 100644 index 0000000000..7e02cb7d10 --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -0,0 +1,60 @@ +#ifndef OPENMW_MWWORLD_CELLPRELOADER_H +#define OPENMW_MWWORLD_CELLPRELOADER_H + +#include +#include +#include + +namespace Resource +{ + class SceneManager; + class BulletShapeManager; +} + +namespace MWWorld +{ + class CellStore; + + class CellPreloader + { + public: + CellPreloader(Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager); + + /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. + /// @note The cell itself must be in State_Loaded or State_Preloaded. + void preload(const MWWorld::CellStore* cell, double timestamp); + + /// Removes preloaded cells that have not had a preload request for a while. + void updateCache(double timestamp); + + void setWorkQueue(osg::ref_ptr workQueue); + + private: + Resource::SceneManager* mSceneManager; + Resource::BulletShapeManager* mBulletShapeManager; + osg::ref_ptr mWorkQueue; + + struct PreloadEntry + { + PreloadEntry(double timestamp, osg::ref_ptr workItem) + : mTimeStamp(timestamp) + , mWorkItem(workItem) + { + } + PreloadEntry() + : mTimeStamp(0.0) + { + } + + double mTimeStamp; + osg::ref_ptr mWorkItem; + }; + typedef std::map PreloadMap; + + // Cells that are currently being preloaded, or have already finished preloading + PreloadMap mPreloadCells; + }; + +} + +#endif diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 5c8d07f86c..dcaa73e933 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -333,6 +333,11 @@ namespace MWWorld return mState; } + const std::vector &CellStore::getPreloadedIds() const + { + return mIds; + } + bool CellStore::hasState() const { return mHasState; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 2f483f0ba7..d753c97457 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -204,6 +204,9 @@ namespace MWWorld State getState() const; + const std::vector& getPreloadedIds() const; + ///< Get Ids of objects in this cell, only valid in State_Preloaded + bool hasState() const; ///< Does this cell have state that needs to be stored in a saved game file? diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 42323a68a0..26e8f7b6a3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,9 +46,7 @@ add_component_dir (resource add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller - lightmanager lightutil positionattitudetransform - # not used yet - #workqueue + lightmanager lightutil positionattitudetransform workqueue ) add_component_dir (nif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index f499c0016b..68ed136440 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -54,6 +54,9 @@ namespace Resource void ResourceSystem::updateCache(double referenceTime) { + // TODO: call updateCache from the worker thread so the main thread isn't held up by the delete operations + // change ObjectCache to not hold lock while the unref happens + osg::Timer timer; for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime); From d855a13b4435d989ebf46346e71437782c231a7f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 00:36:31 +0100 Subject: [PATCH 197/765] Clear the resource cache from the worker thread --- apps/openmw/mwrender/renderingmanager.cpp | 6 +++- apps/openmw/mwrender/renderingmanager.hpp | 2 ++ apps/openmw/mwworld/cellpreloader.cpp | 34 +++++++++++++++++++++-- apps/openmw/mwworld/cellpreloader.hpp | 6 ++-- apps/openmw/mwworld/scene.cpp | 4 +++ apps/openmw/mwworld/scene.hpp | 5 ++-- components/resource/resourcesystem.cpp | 6 +--- 7 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d4199d5f6c..07b477c436 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -233,11 +233,15 @@ namespace MWRender void RenderingManager::clearCache() { - mResourceSystem->updateCache(mViewer->getFrameStamp()->getReferenceTime()); if (mTerrain.get()) mTerrain->clearCache(); } + double RenderingManager::getReferenceTime() const + { + return mViewer->getFrameStamp()->getReferenceTime(); + } + osg::Group* RenderingManager::getLightRoot() { return mLightRoot.get(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 550e48e636..bc5db562c3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -67,6 +67,8 @@ namespace MWRender void clearCache(); + double getReferenceTime() const; + osg::Group* getLightRoot(); void setNightEyeFactor(float factor); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index dd6850abae..16bf920bbb 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -33,6 +34,7 @@ namespace MWWorld std::vector& mOut; }; + /// Worker thread item: preload models in a cell. class PreloadItem : public SceneUtil::WorkItem { public: @@ -92,8 +94,30 @@ namespace MWWorld std::vector > mPreloadedShapes; }; - CellPreloader::CellPreloader(Resource::SceneManager *sceneManager, Resource::BulletShapeManager* bulletShapeManager) - : mSceneManager(sceneManager) + /// Worker thread item: update the resource system's cache, effectively deleting unused entries. + class UpdateCacheItem : public SceneUtil::WorkItem + { + public: + UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime) + : mReferenceTime(referenceTime) + , mResourceSystem(resourceSystem) + { + } + + virtual void doWork() + { + osg::Timer timer; + mResourceSystem->updateCache(mReferenceTime); + std::cout << "cleared cache in " << timer.time_m() << std::endl; + } + + private: + double mReferenceTime; + Resource::ResourceSystem* mResourceSystem; + }; + + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager) + : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) { mWorkQueue = new SceneUtil::WorkQueue; @@ -120,7 +144,7 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mSceneManager, mBulletShapeManager)); + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager)); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); @@ -129,6 +153,7 @@ namespace MWWorld void CellPreloader::updateCache(double timestamp) { // time (in seconds) to keep a preloaded cell in cache after it's no longer needed + // additionally we could support a minimum/maximum size for the cache const double expiryTime = 60.0; for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) @@ -138,6 +163,9 @@ namespace MWWorld else ++it; } + + // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations + mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, timestamp)); } void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 7e02cb7d10..b3413b1743 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -7,7 +7,7 @@ namespace Resource { - class SceneManager; + class ResourceSystem; class BulletShapeManager; } @@ -18,7 +18,7 @@ namespace MWWorld class CellPreloader { public: - CellPreloader(Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager); + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. @@ -30,7 +30,7 @@ namespace MWWorld void setWorkQueue(osg::ref_ptr workQueue); private: - Resource::SceneManager* mSceneManager; + Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; osg::ref_ptr mWorkQueue; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f30e6efe10..70a461b5b9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -24,6 +24,7 @@ #include "class.hpp" #include "cellvisitors.hpp" #include "cellstore.hpp" +#include "cellpreloader.hpp" namespace { @@ -402,6 +403,7 @@ namespace MWWorld mCellChanged = true; + mPreloader->updateCache(mRendering.getReferenceTime()); mRendering.clearCache(); } @@ -439,6 +441,7 @@ namespace MWWorld Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) { + mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); } Scene::~Scene() @@ -515,6 +518,7 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); + mPreloader->updateCache(mRendering.getReferenceTime()); mRendering.clearCache(); } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index c6de3ebdf7..b428d0ddd7 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,12 +1,11 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H -//#include "../mwrender/renderingmanager.hpp" - #include "ptr.hpp" #include "globals.hpp" #include +#include namespace osg { @@ -43,6 +42,7 @@ namespace MWWorld { class Player; class CellStore; + class CellPreloader; class Scene { @@ -57,6 +57,7 @@ namespace MWWorld bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; + std::auto_ptr mPreloader; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 68ed136440..1f25bd461f 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -54,13 +54,9 @@ namespace Resource void ResourceSystem::updateCache(double referenceTime) { - // TODO: call updateCache from the worker thread so the main thread isn't held up by the delete operations - // change ObjectCache to not hold lock while the unref happens - - osg::Timer timer; + // TODO: change ObjectCache to not hold lock while the unref happens for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime); - std::cout << "updateCache took " << timer.time_m() << " ms" << std::endl; } void ResourceSystem::addResourceManager(ResourceManager *resourceMgr) From c155680d3c8c22ac8dc22ccc540584e3b720990f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 00:43:37 +0100 Subject: [PATCH 198/765] Customize ObjectCache for more efficient locking in removeExpiredObjectsInCache --- components/resource/objectcache.cpp | 37 ++++++++++++++----------- components/resource/objectcache.hpp | 24 ++++++---------- components/resource/resourcemanager.cpp | 2 +- components/resource/resourcemanager.hpp | 8 ++---- components/resource/resourcesystem.cpp | 1 - 5 files changed, 32 insertions(+), 40 deletions(-) diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 7264d05aa7..0c6066417c 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -11,13 +11,10 @@ * OpenSceneGraph Public License for more details. */ -#include - -#if OSG_VERSION_LESS_THAN(3,3,3) - #include "objectcache.hpp" -using namespace osgDB; +namespace Resource +{ //////////////////////////////////////////////////////////////////////////////////////////// // @@ -95,21 +92,29 @@ void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double r void ObjectCache::removeExpiredObjectsInCache(double expiryTime) { - OpenThreads::ScopedLock lock(_objectCacheMutex); + std::vector > objectsToRemove; - // Remove expired entries from object cache - ObjectCacheMap::iterator oitr = _objectCache.begin(); - while(oitr != _objectCache.end()) { - if (oitr->second.second<=expiryTime) + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove expired entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) { - _objectCache.erase(oitr++); - } - else - { - ++oitr; + if (oitr->second.second<=expiryTime) + { + objectsToRemove.push_back(oitr->second.first); + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } } } + + // note, actual unref happens outside of the lock + objectsToRemove.clear(); } void ObjectCache::removeFromObjectCache(const std::string& fileName) @@ -138,4 +143,4 @@ void ObjectCache::releaseGLObjects(osg::State* state) } } -#endif +} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index cae48ca6b7..624c38bad5 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -1,3 +1,6 @@ +// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below. +// The main change from the upstream version is that removeExpiredObjectsInCache no longer keeps a lock while the unref happens. + /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under @@ -11,17 +14,8 @@ * OpenSceneGraph Public License for more details. */ -// Wrapper for osgDB/ObjectCache. Works around ObjectCache not being available in old OSG 3.2. -// Use "#include objectcache.hpp" in place of "#include - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) -#include -#else +#ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE +#define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #include @@ -30,9 +24,9 @@ #include -namespace osgDB { +namespace Resource { -class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced +class ObjectCache : public osg::Referenced { public: @@ -70,7 +64,7 @@ class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const std::string& fileName); - /** call rleaseGLObjects on all objects attached to the object cache.*/ + /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); protected: @@ -88,5 +82,3 @@ class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced } #endif - -#endif diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index 60233baa0d..03e6263b91 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -7,7 +7,7 @@ namespace Resource ResourceManager::ResourceManager(const VFS::Manager *vfs, const double expiryDelay) : mVFS(vfs) - , mCache(new osgDB::ObjectCache) + , mCache(new Resource::ObjectCache) , mExpiryDelay(expiryDelay) { diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index e45a2e9cb7..a9b6ab3d51 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -8,13 +8,9 @@ namespace VFS class Manager; } -namespace osgDB -{ - class ObjectCache; -} - namespace Resource { + class ObjectCache; /// @brief Base class for managers that require a virtual file system and object cache. /// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes. @@ -34,7 +30,7 @@ namespace Resource protected: const VFS::Manager* mVFS; - osg::ref_ptr mCache; + osg::ref_ptr mCache; double mExpiryDelay; }; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 1f25bd461f..a8ab6556d7 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -54,7 +54,6 @@ namespace Resource void ResourceSystem::updateCache(double referenceTime) { - // TODO: change ObjectCache to not hold lock while the unref happens for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime); } From d3415387a533a069daf070e37d8cdeed1d9d3d1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 01:18:20 +0100 Subject: [PATCH 199/765] AI: take into account success chance when rating a spell --- apps/openmw/mwmechanics/aicombataction.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 60446e524d..608bbaeb17 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -167,7 +167,8 @@ namespace MWMechanics { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - if (MWMechanics::getSpellSuccessChance(spell, actor) == 0) + float successChance = MWMechanics::getSpellSuccessChance(spell, actor); + if (successChance == 0.f) return 0.f; if (spell->mData.mType != ESM::Spell::ST_Spell) @@ -192,7 +193,7 @@ namespace MWMechanics if ( ((types & Touch) || (types & Target)) && target.getClass().getCreatureStats(target).getActiveSpells().isSpellActive(spell->mId)) return 0.f; - return rateEffects(spell->mEffects, actor, target); + return rateEffects(spell->mEffects, actor, target) * (successChance / 100.f); } float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) From 94c05c6baa50d1f2a736f2d3f1808ef1bed7abe2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 01:19:10 +0100 Subject: [PATCH 200/765] AI: don't cast useless resist spells (Fixes #2760) --- apps/openmw/mwmechanics/aicombataction.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 608bbaeb17..627bc02f79 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -254,8 +254,27 @@ namespace MWMechanics case ESM::MagicEffect::CureCommonDisease: case ESM::MagicEffect::CureBlightDisease: case ESM::MagicEffect::CureCorprusDisease: + case ESM::MagicEffect::ResistBlightDisease: + case ESM::MagicEffect::ResistCommonDisease: + case ESM::MagicEffect::ResistCorprusDisease: case ESM::MagicEffect::Invisibility: + case ESM::MagicEffect::Chameleon: return 0.f; + + case ESM::MagicEffect::RestoreAttribute: + return 0.f; // TODO: implement based on attribute damage + case ESM::MagicEffect::RestoreSkill: + return 0.f; // TODO: implement based on skill damage + + case ESM::MagicEffect::ResistFire: + case ESM::MagicEffect::ResistFrost: + case ESM::MagicEffect::ResistMagicka: + case ESM::MagicEffect::ResistNormalWeapons: + case ESM::MagicEffect::ResistParalysis: + case ESM::MagicEffect::ResistPoison: + case ESM::MagicEffect::ResistShock: + return 0.f; // probably useless since we don't know in advance what the enemy will cast + case ESM::MagicEffect::Feather: if (actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor) >= 0) return 100.f; From 162287b82d60d068474836751c7fad41b50aa275 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 01:24:49 +0100 Subject: [PATCH 201/765] AI combat actions: rename 'target' to 'enemy' --- apps/openmw/mwmechanics/aicombataction.cpp | 46 +++++++++++----------- apps/openmw/mwmechanics/aicombataction.hpp | 12 +++--- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 627bc02f79..cf0d2532b9 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -97,7 +97,7 @@ namespace MWMechanics return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); } - float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, int type, + float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { if (item.getTypeName() != typeid(ESM::Weapon).name()) @@ -153,7 +153,7 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes && (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) - rating += rateEffects(enchantment->mEffects, actor, target); + rating += rateEffects(enchantment->mEffects, actor, enemy); } int skill = item.getClass().getEquipmentSkill(item); @@ -163,7 +163,7 @@ namespace MWMechanics return rating; } - float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); @@ -190,13 +190,13 @@ namespace MWMechanics int types = getRangeTypes(spell->mEffects); if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) return 0.f; - if ( ((types & Touch) || (types & Target)) && target.getClass().getCreatureStats(target).getActiveSpells().isSpellActive(spell->mId)) + if ( ((types & Touch) || (types & Target)) && enemy.getClass().getCreatureStats(enemy).getActiveSpells().isSpellActive(spell->mId)) return 0.f; - return rateEffects(spell->mEffects, actor, target) * (successChance / 100.f); + return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f); } - float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& target) + float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { if (ptr.getClass().getEnchantment(ptr).empty()) return 0.f; @@ -205,7 +205,7 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { - return rateEffects(enchantment->mEffects, actor, target); + return rateEffects(enchantment->mEffects, actor, enemy); } else { @@ -214,9 +214,9 @@ namespace MWMechanics } } - float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { - // NOTE: target may be empty + // NOTE: enemy may be empty float rating = 1; switch (effect.mEffectID) @@ -337,7 +337,7 @@ namespace MWMechanics case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::DrainAttribute: - if (!target.isEmpty() && target.getClass().getCreatureStats(target).getAttribute(effect.mAttribute).getModified() <= 0) + if (!enemy.isEmpty() && enemy.getClass().getCreatureStats(enemy).getAttribute(effect.mAttribute).getModified() <= 0) return 0.f; { if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) @@ -359,9 +359,9 @@ namespace MWMechanics case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::DrainSkill: - if (target.isEmpty() || !target.getClass().isNpc()) + if (enemy.isEmpty() || !enemy.getClass().isNpc()) return 0.f; - if (target.getClass().getNpcStats(target).getSkill(effect.mSkill).getModified() <= 0) + if (enemy.getClass().getNpcStats(enemy).getSkill(effect.mSkill).getModified() <= 0) return 0.f; break; @@ -369,9 +369,9 @@ namespace MWMechanics break; } - // TODO: for non-cumulative effects (e.g. paralyze), check if the target is already suffering from them + // TODO: for non-cumulative effects (e.g. paralyze), check if the enemy is already suffering from them - // TODO: could take into account target's resistance/weakness against the effect + // TODO: could take into account enemy's resistance/weakness against the effect const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); @@ -392,13 +392,13 @@ namespace MWMechanics return rating; } - float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { - // NOTE: target may be empty + // NOTE: enemy may be empty float rating = 0.f; for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { - rating += rateEffect(*it, actor, target); + rating += rateEffect(*it, actor, enemy); } return rating; } @@ -474,7 +474,7 @@ namespace MWMechanics // Already done in AiCombat itself } - boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) + boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); @@ -503,7 +503,7 @@ namespace MWMechanics for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateMagicItem(*it, actor, target); + float rating = rateMagicItem(*it, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; @@ -515,7 +515,7 @@ namespace MWMechanics MWWorld::Ptr bestArrow; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateWeapon(*it, actor, target, ESM::Weapon::Arrow); + float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); if (rating > bestArrowRating) { bestArrowRating = rating; @@ -527,7 +527,7 @@ namespace MWMechanics MWWorld::Ptr bestBolt; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - float rating = rateWeapon(*it, actor, target, ESM::Weapon::Bolt); + float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); if (rating > bestBoltRating) { bestBoltRating = rating; @@ -542,7 +542,7 @@ namespace MWMechanics == equipmentSlots.end()) continue; - float rating = rateWeapon(*it, actor, target, -1, bestArrowRating, bestBoltRating); + float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { const ESM::Weapon* weapon = it->get()->mBase; @@ -563,7 +563,7 @@ namespace MWMechanics { const ESM::Spell* spell = it->first; - float rating = rateSpell(spell, actor, target); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index a4a398d05f..6f5cbfbc29 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -72,19 +72,19 @@ namespace MWMechanics virtual void getCombatRange (float& rangeAttack, float& rangeFollow); }; - float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr &target); + float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); /// @param type Skip all weapons that are not of this type (i.e. return rating 0) - float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& target, + float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); /// @note target may be empty - float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); /// @note target may be empty - float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } #endif From a7b217def22d6bf1c35afd5dde36ceb18000a6eb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 01:27:40 +0100 Subject: [PATCH 202/765] AI: don't cast fortify effects (Fixes #3184) --- apps/openmw/mwmechanics/aicombataction.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index cf0d2532b9..cee2aae9c4 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -275,6 +275,15 @@ namespace MWMechanics case ESM::MagicEffect::ResistShock: return 0.f; // probably useless since we don't know in advance what the enemy will cast + // don't cast these for now as they would make the NPC cast the same effect over and over again, especially when they have potions + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::FortifyMaximumMagicka: + return 0.f; + case ESM::MagicEffect::Feather: if (actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor) >= 0) return 100.f; From 023c87b21595353b98edce6176b38f8dca52f288 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 05:13:46 -0800 Subject: [PATCH 203/765] Preload cell when the player goes near a teleport door. It works! --- apps/openmw/mwworld/cellpreloader.cpp | 2 + apps/openmw/mwworld/scene.cpp | 58 +++++++++++++++++++++++++++ apps/openmw/mwworld/scene.hpp | 3 ++ 3 files changed, 63 insertions(+) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 16bf920bbb..f1366a8b2a 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -66,6 +66,7 @@ namespace MWWorld /// Preload work to be called from the worker thread. virtual void doWork() { + osg::Timer preloadTimer; for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) { try @@ -81,6 +82,7 @@ namespace MWWorld // error will be shown when visiting the cell } } + std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; } private: diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 70a461b5b9..912a39e7c1 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -193,6 +193,13 @@ namespace MWWorld void Scene::update (float duration, bool paused) { + mPreloadTimer += duration; + if (mPreloadTimer > 1.f) + { + preloadCells(); + mPreloadTimer = 0.f; + } + mRendering.update (duration, paused); } @@ -440,6 +447,7 @@ namespace MWWorld Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) + , mPreloadTimer(0.f) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); } @@ -603,4 +611,54 @@ namespace MWWorld return Ptr(); } + + void Scene::preloadCells() + { + std::vector teleportDoors; + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + { + const MWWorld::CellStore* cellStore = *iter; + typedef MWWorld::CellRefList::List DoorList; + const DoorList &doors = cellStore->getReadOnlyDoors().mList; + for (DoorList::const_iterator doorIt = doors.begin(); doorIt != doors.end(); ++doorIt) + { + if (!doorIt->mRef.getTeleport()) { + continue; + } + teleportDoors.push_back(MWWorld::ConstPtr(&*doorIt, cellStore)); + } + } + + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const float preloadDist = 500.f; + + for (std::vector::iterator it = teleportDoors.begin(); it != teleportDoors.end(); ++it) + { + const MWWorld::ConstPtr& door = *it; + float sqrDistToPlayer = (player.getRefData().getPosition().asVec3() - door.getRefData().getPosition().asVec3()).length2(); + + if (sqrDistToPlayer < preloadDist*preloadDist) + { + MWWorld::CellStore* targetCell = NULL; + try + { + if (!door.getCellRef().getDestCell().empty()) + targetCell = MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()); + else + { + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y); + targetCell = MWBase::Environment::get().getWorld()->getExterior(x, y); + } + } + catch (std::exception& e) + { + // ignore error for now, would spam the log too much + } + if (targetCell) + mPreloader->preload(targetCell, mRendering.getReferenceTime()); + } + } + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index b428d0ddd7..4ac0d0666a 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -58,6 +58,7 @@ namespace MWWorld MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; std::auto_ptr mPreloader; + float mPreloadTimer; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); @@ -112,6 +113,8 @@ namespace MWWorld bool isCellActive(const CellStore &cell); Ptr searchPtrViaActorId (int actorId); + + void preloadCells(); }; } From 8592166eeb5acf5bbffb16125b0ccf5f98dc2b0b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 05:27:19 -0800 Subject: [PATCH 204/765] Preload surrounding cells when preloading an exterior cell destination --- apps/openmw/mwworld/cellpreloader.cpp | 2 ++ apps/openmw/mwworld/scene.cpp | 29 +++++++++++++++------------ apps/openmw/mwworld/scene.hpp | 1 + 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index f1366a8b2a..429e3294db 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -66,6 +66,8 @@ namespace MWWorld /// Preload work to be called from the worker thread. virtual void doWork() { + // TODO: make CellStore::loadRefs thread safe so we can call it from here + osg::Timer preloadTimer; for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 912a39e7c1..38b762018e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -334,15 +334,13 @@ namespace MWWorld std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); - const int halfGridSize = Settings::Manager::getInt("exterior cell load distance", "Cells"); - CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) { - if (std::abs (X-(*active)->getCell()->getGridX())<=halfGridSize && - std::abs (Y-(*active)->getCell()->getGridY())<=halfGridSize) + if (std::abs (X-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (Y-(*active)->getCell()->getGridY())<=mHalfGridSize) { // keep cells within the new grid ++active; @@ -354,9 +352,9 @@ namespace MWWorld int refsToLoad = 0; // get the number of refs to load - for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) + for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) { - for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) + for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -379,9 +377,9 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cells - for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) + for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) { - for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) + for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -448,6 +446,7 @@ namespace MWWorld Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) , mPreloadTimer(0.f) + , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); } @@ -640,24 +639,28 @@ namespace MWWorld if (sqrDistToPlayer < preloadDist*preloadDist) { - MWWorld::CellStore* targetCell = NULL; try { if (!door.getCellRef().getDestCell().empty()) - targetCell = MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()); + mPreloader->preload(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()), mRendering.getReferenceTime()); else { int x,y; MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y); - targetCell = MWBase::Environment::get().getWorld()->getExterior(x, y); + + for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) + { + for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) + { + mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime()); + } + } } } catch (std::exception& e) { // ignore error for now, would spam the log too much } - if (targetCell) - mPreloader->preload(targetCell, mRendering.getReferenceTime()); } } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 4ac0d0666a..68748af850 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -59,6 +59,7 @@ namespace MWWorld MWRender::RenderingManager& mRendering; std::auto_ptr mPreloader; float mPreloadTimer; + int mHalfGridSize; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); From 8b981ab507bf7e940c9ed86f466c585f9258ffeb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 05:53:42 -0800 Subject: [PATCH 205/765] Crash fix --- components/resource/scenemanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4ed14241a2..c2be3960f6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -200,7 +200,8 @@ namespace Resource for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); - apply(texture); + if (texture) + apply(texture); } } From c3ad4dad7544bd2e0ea1f7931ce8854d87e5d535 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 05:53:56 -0800 Subject: [PATCH 206/765] Fix applying of filter settings on terrain textures --- components/terrain/terraingrid.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index eb085ca426..4f7a0772b7 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -139,15 +139,16 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu std::vector > layerTextures; for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - osg::ref_ptr tex = mTextureCache[it->mDiffuseMap]; - if (!tex) + osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; + if (!texture) { - tex = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mTextureCache[it->mDiffuseMap] = tex; + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mDiffuseMap] = texture; } - layerTextures.push_back(tex); + layerTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } @@ -161,7 +162,6 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setResizeNonPowerOfTwoHint(false); texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); - mResourceSystem->getSceneManager()->applyFilterSettings(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); } From 49ecac4ced6a562f6b0e6491e87ab6d3864f61c9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 07:37:35 -0800 Subject: [PATCH 207/765] Add a mutex lock around the SharedStateManager --- components/resource/scenemanager.cpp | 2 ++ components/resource/scenemanager.hpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index c2be3960f6..eecee813f4 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -357,7 +357,9 @@ namespace Resource loaded->accept(setFilterSettingsControllerVisitor); // share state + mSharedStateMutex.lock(); osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); + mSharedStateMutex.unlock(); if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 173131e666..0345fff228 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -89,6 +89,8 @@ namespace Resource void setUnRefImageDataAfterApply(bool unref); private: + OpenThreads::Mutex mSharedStateMutex; + Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; From 610257cd3a396c8e4fe557584a89395371af88b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 07:37:56 -0800 Subject: [PATCH 208/765] Preload the exterior cell grid --- apps/openmw/mwworld/scene.cpp | 53 +++++++++++++++++++++++++++++++---- apps/openmw/mwworld/scene.hpp | 8 ++++-- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 38b762018e..b859efbf13 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -194,7 +194,7 @@ namespace MWWorld void Scene::update (float duration, bool paused) { mPreloadTimer += duration; - if (mPreloadTimer > 1.f) + if (mPreloadTimer > 0.5f) { preloadCells(); mPreloadTimer = 0.f; @@ -313,7 +313,7 @@ namespace MWWorld getGridCenter(cellX, cellY); float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); - const float maxDistance = 8192/2 + 1024; // 1/2 cell size + threshold + const float maxDistance = 8192/2 + mCellLoadingThreshold; // 1/2 cell size + threshold float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); if (distance > maxDistance) { @@ -447,6 +447,8 @@ namespace MWWorld : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) , mPreloadTimer(0.f) , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) + , mCellLoadingThreshold(1024.f) + , mPreloadDistance(1000.f) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); } @@ -612,6 +614,12 @@ namespace MWWorld } void Scene::preloadCells() + { + preloadTeleportDoorDestinations(); + preloadExteriorGrid(); + } + + void Scene::preloadTeleportDoorDestinations() { std::vector teleportDoors; for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); @@ -630,14 +638,12 @@ namespace MWWorld } const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const float preloadDist = 500.f; - for (std::vector::iterator it = teleportDoors.begin(); it != teleportDoors.end(); ++it) { const MWWorld::ConstPtr& door = *it; float sqrDistToPlayer = (player.getRefData().getPosition().asVec3() - door.getRefData().getPosition().asVec3()).length2(); - if (sqrDistToPlayer < preloadDist*preloadDist) + if (sqrDistToPlayer < mPreloadDistance*mPreloadDistance) { try { @@ -664,4 +670,41 @@ namespace MWWorld } } } + + void Scene::preloadExteriorGrid() + { + if (!MWBase::Environment::get().getWorld()->isCellExterior()) + return; + + int halfGridSizePlusOne = mHalfGridSize + 1; + + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); + + int cellX,cellY; + getGridCenter(cellX,cellY); + + float centerX, centerY; + MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); + + for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx) + { + for (int dy = -halfGridSizePlusOne; dy <= halfGridSizePlusOne; ++dy) + { + if (dy != halfGridSizePlusOne && dy != -halfGridSizePlusOne && dx != halfGridSizePlusOne && dx != -halfGridSizePlusOne) + continue; // only care about the outer (not yet loaded) part of the grid + + float thisCellCenterX, thisCellCenterY; + MWBase::Environment::get().getWorld()->indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true); + + float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); + float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance; + + std::cout << cellX+dx << " " << cellY+dy << " dist " << dist << " need " << loadDist << std::endl; + + if (dist < loadDist) + mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy), mRendering.getReferenceTime()); + } + } + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 68748af850..36021819e8 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -60,6 +60,8 @@ namespace MWWorld std::auto_ptr mPreloader; float mPreloadTimer; int mHalfGridSize; + float mCellLoadingThreshold; + float mPreloadDistance; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); @@ -68,6 +70,10 @@ namespace MWWorld void getGridCenter(int& cellX, int& cellY); + void preloadCells(); + void preloadTeleportDoorDestinations(); + void preloadExteriorGrid(); + public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics); @@ -114,8 +120,6 @@ namespace MWWorld bool isCellActive(const CellStore &cell); Ptr searchPtrViaActorId (int actorId); - - void preloadCells(); }; } From 5efaa9817c6172f5be24a0efdff7f308db4e71f1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 18:01:14 +0100 Subject: [PATCH 209/765] Add preloading settings --- apps/openmw/mwworld/scene.cpp | 16 +++++++++------- apps/openmw/mwworld/scene.hpp | 1 + files/settings-default.cfg | 6 ++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b859efbf13..c788df234a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -193,11 +193,14 @@ namespace MWWorld void Scene::update (float duration, bool paused) { - mPreloadTimer += duration; - if (mPreloadTimer > 0.5f) + if (mPreloadEnabled) { - preloadCells(); - mPreloadTimer = 0.f; + mPreloadTimer += duration; + if (mPreloadTimer > 0.5f) + { + preloadCells(); + mPreloadTimer = 0.f; + } } mRendering.update (duration, paused); @@ -448,7 +451,8 @@ namespace MWWorld , mPreloadTimer(0.f) , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) , mCellLoadingThreshold(1024.f) - , mPreloadDistance(1000.f) + , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) + , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); } @@ -700,8 +704,6 @@ namespace MWWorld float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance; - std::cout << cellX+dx << " " << cellY+dy << " dist " << dist << " need " << loadDist << std::endl; - if (dist < loadDist) mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy), mRendering.getReferenceTime()); } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 36021819e8..34e137e45a 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -62,6 +62,7 @@ namespace MWWorld int mHalfGridSize; float mCellLoadingThreshold; float mPreloadDistance; + bool mPreloadEnabled; void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index dddf1a29b9..c033426005 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -37,6 +37,12 @@ first person field of view = 55.0 # dramatically affect performance, see documentation for details. exterior cell load distance = 1 +# Preload cells in a background thread +preload enabled = true + +# Preloading distance threshold +preload distance = 1000 + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). From 778bce3ae939ec17345b09cb1365215079ee9640 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 18:27:12 +0100 Subject: [PATCH 210/765] Remove unused ObjectCache functions --- components/resource/objectcache.cpp | 24 ------------------------ components/resource/objectcache.hpp | 6 ------ 2 files changed, 30 deletions(-) diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 0c6066417c..54576a122c 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -31,42 +31,18 @@ ObjectCache::~ObjectCache() // OSG_NOTICE<<"Destructed ObjectCache"< lock1(_objectCacheMutex); - OpenThreads::ScopedLock lock2(objectCache->_objectCacheMutex); - - // OSG_NOTICE<<"Inserting objects to main ObjectCache "<_objectCache.size()<_objectCache.begin(), objectCache->_objectCache.end()); -} - - void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) { OpenThreads::ScopedLock lock(_objectCacheMutex); _objectCache[filename]=ObjectTimeStampPair(object,timestamp); } -osg::Object* ObjectCache::getFromObjectCache(const std::string& fileName) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - ObjectCacheMap::iterator itr = _objectCache.find(fileName); - if (itr!=_objectCache.end()) return itr->second.first.get(); - else return 0; -} - osg::ref_ptr ObjectCache::getRefFromObjectCache(const std::string& fileName) { OpenThreads::ScopedLock lock(_objectCacheMutex); ObjectCacheMap::iterator itr = _objectCache.find(fileName); if (itr!=_objectCache.end()) { - // OSG_NOTICE<<"Found "<second.first; } else return 0; diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 624c38bad5..b933933f3f 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -49,18 +49,12 @@ class ObjectCache : public osg::Referenced /** Remove all objects in the cache regardless of having external references or expiry times.*/ void clear(); - /** Add contents of specified ObjectCache to this object cache.*/ - void addObjectCache(ObjectCache* object); - /** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); /** Remove Object from cache.*/ void removeFromObjectCache(const std::string& fileName); - /** Get an Object from the object cache*/ - osg::Object* getFromObjectCache(const std::string& fileName); - /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const std::string& fileName); From 41233fc8e5717ba0d37dd5c3afb513a7e5702c62 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 18:56:21 +0100 Subject: [PATCH 211/765] Keep a reference to the original scene template for as long as the instance is used --- components/resource/objectcache.cpp | 2 -- components/resource/scenemanager.cpp | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 54576a122c..17368f34f5 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -23,12 +23,10 @@ namespace Resource ObjectCache::ObjectCache(): osg::Referenced(true) { -// OSG_NOTICE<<"Constructed ObjectCache"< scene = getTemplate(name); osg::ref_ptr cloned = osg::clone(scene.get(), SceneUtil::CopyOp()); + + // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + cloned->getOrCreateUserDataContainer()->addUserObject(const_cast(scene.get())); + return cloned; } From a81b10b4153751ee7c1cc117f8b9096dda70bb8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 19:05:55 +0100 Subject: [PATCH 212/765] Make the cache expiryDelay configurable --- apps/openmw/mwworld/cellpreloader.cpp | 12 ++++++++---- apps/openmw/mwworld/cellpreloader.hpp | 4 ++++ apps/openmw/mwworld/scene.cpp | 4 ++++ components/resource/niffilemanager.cpp | 3 +-- components/resource/resourcemanager.cpp | 9 +++++++-- components/resource/resourcemanager.hpp | 6 ++++-- components/resource/resourcesystem.cpp | 10 ++++++++++ components/resource/resourcesystem.hpp | 3 +++ files/settings-default.cfg | 4 ++++ 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 429e3294db..13ce75d685 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -123,6 +123,7 @@ namespace MWWorld CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) + , mExpiryDelay(0.0) { mWorkQueue = new SceneUtil::WorkQueue; } @@ -156,13 +157,11 @@ namespace MWWorld void CellPreloader::updateCache(double timestamp) { - // time (in seconds) to keep a preloaded cell in cache after it's no longer needed - // additionally we could support a minimum/maximum size for the cache - const double expiryTime = 60.0; + // TODO: add settings for a minimum/maximum size of the cache for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { - if (it->second.mTimeStamp < timestamp - expiryTime) + if (it->second.mTimeStamp < timestamp - mExpiryDelay) mPreloadCells.erase(it++); else ++it; @@ -172,6 +171,11 @@ namespace MWWorld mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, timestamp)); } + void CellPreloader::setExpiryDelay(double expiryDelay) + { + mExpiryDelay = expiryDelay; + } + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) { mWorkQueue = workQueue; diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index b3413b1743..5a24321795 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -27,12 +27,16 @@ namespace MWWorld /// Removes preloaded cells that have not had a preload request for a while. void updateCache(double timestamp); + /// How long to keep a preloaded cell in cache after it's no longer requested. + void setExpiryDelay(double expiryDelay); + void setWorkQueue(osg::ref_ptr workQueue); private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; osg::ref_ptr mWorkQueue; + double mExpiryDelay; struct PreloadEntry { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index c788df234a..86f40df73e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -455,6 +455,10 @@ namespace MWWorld , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); + + float cacheExpiryDelay = Settings::Manager::getFloat("cache expiry delay", "Cells"); + rendering.getResourceSystem()->setExpiryDelay(cacheExpiryDelay); + mPreloader->setExpiryDelay(cacheExpiryDelay); } Scene::~Scene() diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 3c7437520b..ecb63db600 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -29,8 +29,7 @@ namespace Resource }; NifFileManager::NifFileManager(const VFS::Manager *vfs) - : ResourceManager(vfs, 0.0) // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, - // so we'll use expiryDelay of 0 to instantly delete NIF files after use. + : ResourceManager(vfs) { } diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index 03e6263b91..b6b38bfa2b 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -5,10 +5,10 @@ namespace Resource { - ResourceManager::ResourceManager(const VFS::Manager *vfs, const double expiryDelay) + ResourceManager::ResourceManager(const VFS::Manager *vfs) : mVFS(vfs) , mCache(new Resource::ObjectCache) - , mExpiryDelay(expiryDelay) + , mExpiryDelay(0.0) { } @@ -26,6 +26,11 @@ namespace Resource mCache->clear(); } + void ResourceManager::setExpiryDelay(double expiryDelay) + { + mExpiryDelay = expiryDelay; + } + const VFS::Manager* ResourceManager::getVFS() const { return mVFS; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index a9b6ab3d51..d2a1d10237 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -17,8 +17,7 @@ namespace Resource class ResourceManager { public: - /// @param expiryDelay how long to keep objects in cache after no longer being referenced. - ResourceManager(const VFS::Manager* vfs, const double expiryDelay = 300.0); + ResourceManager(const VFS::Manager* vfs); /// Clear cache entries that have not been referenced for longer than expiryDelay. virtual void updateCache(double referenceTime); @@ -26,6 +25,9 @@ namespace Resource /// Clear all cache entries regardless of having external references. virtual void clearCache(); + /// How long to keep objects in cache after no longer being referenced. + void setExpiryDelay (double expiryDelay); + const VFS::Manager* getVFS() const; protected: diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index a8ab6556d7..5772f2b853 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -52,6 +52,16 @@ namespace Resource return mKeyframeManager.get(); } + void ResourceSystem::setExpiryDelay(double expiryDelay) + { + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->setExpiryDelay(expiryDelay); + + // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, + // so no point in using an expiry delay + mNifFileManager->setExpiryDelay(0.0); + } + void ResourceSystem::updateCache(double referenceTime) { for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 30607432da..9b933ffc4d 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -43,6 +43,9 @@ namespace Resource /// @note Does not delete resourceMgr. void removeResourceManager(ResourceManager* resourceMgr); + /// How long to keep objects in cache after no longer being referenced. + void setExpiryDelay(double expiryDelay); + /// @note May be called from any thread. const VFS::Manager* getVFS() const; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c033426005..4df20e7096 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -43,6 +43,10 @@ preload enabled = true # Preloading distance threshold preload distance = 1000 +# How long to keep preloaded cells and cached models/textures/collision shapes in cache +# after they're no longer referenced/required (in seconds) +cache expiry delay = 300 + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). From a34a08c212074693e88289371f9702acff1eb959 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 7 Feb 2016 13:52:18 -0500 Subject: [PATCH 213/765] Render cell markers Adds rendering of cell markers. Markers are displayed at center of cell and contain cell's coordinates. --- apps/opencs/CMakeLists.txt | 5 ++- apps/opencs/view/render/cell.cpp | 7 +++ apps/opencs/view/render/cell.hpp | 5 +++ apps/opencs/view/render/cellmarker.cpp | 62 ++++++++++++++++++++++++++ apps/opencs/view/render/cellmarker.hpp | 48 ++++++++++++++++++++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/render/cellmarker.cpp create mode 100644 apps/opencs/view/render/cellmarker.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0bde541bf7..c9245ca9fd 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -35,7 +35,7 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel mergeoperation + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools @@ -90,7 +90,7 @@ opencs_units (view/render opencs_units_noqt (view/render lighting lightingday lightingnight - lightingbright object cell terrainstorage tagbase cellarrow + lightingbright object cell terrainstorage tagbase cellarrow cellmarker ) opencs_hdrs_noqt (view/render @@ -192,6 +192,7 @@ endif(APPLE) target_link_libraries(openmw-cs ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} + ${OSGTEXT_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index bd85c8a142..0030fd9b82 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -70,6 +70,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCellNode = new osg::Group; rootNode->addChild(mCellNode); + setCellMarker(); + if (!mDeleted) { CSMWorld::IdTable& references = dynamic_cast ( @@ -303,6 +305,11 @@ void CSVRender::Cell::setCellArrows (int mask) } } +void CSVRender::Cell::setCellMarker() +{ + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates)); +} + CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const { return mCoordinates; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 85b9bf21b4..22f9872e3d 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -15,6 +15,7 @@ #include "object.hpp" #include "cellarrow.hpp" +#include "cellmarker.hpp" class QModelIndex; @@ -42,6 +43,7 @@ namespace CSVRender std::auto_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::auto_ptr mCellArrows[4]; + std::auto_ptr mCellMarker; bool mDeleted; /// Ignored if cell does not have an object with the given ID. @@ -105,6 +107,9 @@ namespace CSVRender void setCellArrows (int mask); + /// \brief Set marker for this cell. + void setCellMarker(); + /// Returns 0, 0 in case of an unpaged cell. CSMWorld::CellCoordinates getCoordinates() const; diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp new file mode 100644 index 0000000000..17bb05440e --- /dev/null +++ b/apps/opencs/view/render/cellmarker.cpp @@ -0,0 +1,62 @@ +#include "cellmarker.hpp" + +#include + +#include +#include +#include +#include + +void CSVRender::CellMarker::buildMarker() +{ + const int characterSize = 20; + + // Set up marker text containing cell's coordinates. + osg::ref_ptr markerText (new osgText::Text); + markerText->setBackdropType(osgText::Text::OUTLINE); + markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); + markerText->setCharacterSize(characterSize); + std::string coordinatesText = + "#" + boost::lexical_cast(mCoordinates.getX()) + + " " + boost::lexical_cast(mCoordinates.getY()); + markerText->setText(coordinatesText); + + // Add text to marker node. + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(markerText); + mMarkerNode->addChild(geode); +} + +void CSVRender::CellMarker::positionMarker() +{ + const int cellSize = 8192; + const int markerHeight = 0; + + // Move marker to center of cell. + int x = (mCoordinates.getX() * cellSize) + (cellSize / 2); + int y = (mCoordinates.getY() * cellSize) + (cellSize / 2); + mMarkerNode->setPosition(osg::Vec3f(x, y, markerHeight)); +} + +CSVRender::CellMarker::CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates +) : mCellNode(cellNode), + mCoordinates(coordinates) +{ + // Set up node for cell marker. + mMarkerNode = new osg::AutoTransform(); + mMarkerNode->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN); + mMarkerNode->setAutoScaleToScreen(true); + mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + mCellNode->addChild(mMarkerNode); + + buildMarker(); + positionMarker(); +} + +CSVRender::CellMarker::~CellMarker() +{ + mCellNode->removeChild(mMarkerNode); +} diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp new file mode 100644 index 0000000000..4ac9d86b00 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.hpp @@ -0,0 +1,48 @@ +#ifndef OPENCS_VIEW_CELLMARKER_H +#define OPENCS_VIEW_CELLMARKER_H + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class AutoTransform; + class Group; +} + +namespace CSVRender +{ + /// \brief Marker to display cell coordinates. + class CellMarker + { + private: + + osg::Group* mCellNode; + osg::ref_ptr mMarkerNode; + CSMWorld::CellCoordinates mCoordinates; + + // Not implemented. + CellMarker(const CellMarker&); + CellMarker& operator=(const CellMarker&); + + /// \brief Build marker containing cell's coordinates. + void buildMarker(); + + /// \brief Position marker above center of cell. + void positionMarker(); + + public: + + /// \brief Constructor. + /// \param cellNode Cell to create marker for. + /// \param coordinates Coordinates of cell. + CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates); + + ~CellMarker(); + }; +} + +#endif From c8054424c946ed5433b325d2f1301f16d7d78b64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 22:37:52 +0100 Subject: [PATCH 214/765] Preload items equipped by NPCs --- apps/openmw/mwworld/cellpreloader.cpp | 68 ++++++++++++++++++++++++--- apps/openmw/mwworld/cellpreloader.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 7 +++ components/resource/scenemanager.cpp | 3 ++ 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 13ce75d685..6bf28cb48e 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -5,10 +5,14 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/esmstore.hpp" + #include "cellstore.hpp" #include "manualref.hpp" #include "class.hpp" @@ -23,11 +27,54 @@ namespace MWWorld { } - virtual bool operator()(const MWWorld::ConstPtr& ptr) + virtual bool operator()(const MWWorld::Ptr& ptr) { std::string model = ptr.getClass().getModel(ptr); if (!model.empty()) mOut.push_back(model); + + // TODO: preload NPC body parts (mHead / mHair) + + // FIXME: use const version of InventoryStore functions once they are available + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) + { + std::vector parts; + if(equipped->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = equipped->get()->mBase; + parts = clothes->mParts.mParts; + } + else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + { + const ESM::Armor *armor = equipped->get()->mBase; + parts = armor->mParts.mParts; + } + else + { + model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + mOut.push_back(model); + } + + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(it->mMale); + if (part && !part->mModel.empty()) + mOut.push_back("meshes/"+part->mModel); + part = MWBase::Environment::get().getWorld()->getStore().get().search(it->mFemale); + if (part && !part->mModel.empty()) + mOut.push_back("meshes/"+part->mModel); + } + } + } + } + return true; } @@ -39,14 +86,15 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(const MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager) : mSceneManager(sceneManager) , mBulletShapeManager(bulletShapeManager) { + osg::Timer timer; + ListModelsVisitor visitor (mMeshes); if (cell->getState() == MWWorld::CellStore::State_Loaded) { - ListModelsVisitor visitor (mMeshes); - cell->forEachConst(visitor); + cell->forEach(visitor); } else { @@ -61,6 +109,7 @@ namespace MWWorld mMeshes.push_back(model); } } + std::cout << "listed models in " << timer.time_m() << std::endl; } /// Preload work to be called from the worker thread. @@ -73,9 +122,16 @@ namespace MWWorld { try { + std::string mesh = *it; + Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); + + //std::cout << "preloading " << mesh << std::endl; + mPreloadedNodes.push_back(mSceneManager->getTemplate(*it)); mPreloadedShapes.push_back(mBulletShapeManager->getShape(*it)); + // TODO: load .kf + // TODO: do a createInstance() and hold on to it since we can make use of it when the cell goes active } catch (std::exception& e) @@ -84,7 +140,7 @@ namespace MWWorld // error will be shown when visiting the cell } } - std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; + //std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; } private: @@ -128,7 +184,7 @@ namespace MWWorld mWorkQueue = new SceneUtil::WorkQueue; } - void CellPreloader::preload(const CellStore *cell, double timestamp) + void CellPreloader::preload(CellStore *cell, double timestamp) { if (!mWorkQueue) { diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 5a24321795..c7495182b3 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -22,7 +22,7 @@ namespace MWWorld /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. - void preload(const MWWorld::CellStore* cell, double timestamp); + void preload(MWWorld::CellStore* cell, double timestamp); /// Removes preloaded cells that have not had a preload request for a while. void updateCache(double timestamp); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 86f40df73e..321c1c622e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -329,6 +330,7 @@ namespace MWWorld void Scene::changeCellGrid (int X, int Y) { + osg::Timer timer; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -413,6 +415,8 @@ namespace MWWorld mPreloader->updateCache(mRendering.getReferenceTime()); mRendering.clearCache(); + + std::cout << "changeCellGrid took " << timer.time_m() << std::endl; } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -477,6 +481,7 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + osg::Timer timer; CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); if(!loadcell) @@ -537,6 +542,8 @@ namespace MWWorld mPreloader->updateCache(mRendering.getReferenceTime()); mRendering.clearCache(); + + std::cout << "change to interior took " << timer.time_m() << std::endl; } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 46d975631e..f083e8175d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1,5 +1,6 @@ #include "scenemanager.hpp" +#include #include #include #include @@ -364,6 +365,8 @@ namespace Resource if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); + //std::cout << "loading " << normalized << std::endl; + mCache->addEntryToObjectCache(normalized, loaded); return loaded; } From e4e313fe129fa2c96440e53a8fc9e2ba8bca2fd1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 14:41:21 +0100 Subject: [PATCH 215/765] Remove outdated comment --- apps/openmw/mwworld/cellpreloader.cpp | 2 +- components/resource/resourcemanager.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 6bf28cb48e..d9875e0b71 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -140,7 +140,7 @@ namespace MWWorld // error will be shown when visiting the cell } } - //std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; + std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; } private: diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index b6b38bfa2b..e5f6d9f437 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -15,8 +15,6 @@ namespace Resource void ResourceManager::updateCache(double referenceTime) { - // NOTE: we could clear the cache from the background thread if the deletion proves too much of an overhead - // idea: customize objectCache to not hold a lock while doing the actual deletion mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); } From ef5de945483ab719df31cb3c392a2a0e8b46c9dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 15:31:09 +0100 Subject: [PATCH 216/765] Fix correctActorModelPath --- apps/openmw/mwworld/cellpreloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index d9875e0b71..b45d1de5ba 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -123,7 +123,7 @@ namespace MWWorld try { std::string mesh = *it; - Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); + mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); //std::cout << "preloading " << mesh << std::endl; From fc0be77e4c02ee182141c103be3404757240708b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 15:51:31 +0100 Subject: [PATCH 217/765] Preload keyframes --- apps/openmw/mwworld/cellpreloader.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index b45d1de5ba..4780582c52 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -86,9 +88,10 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager) : mSceneManager(sceneManager) , mBulletShapeManager(bulletShapeManager) + , mKeyframeManager(keyframeManager) { osg::Timer timer; ListModelsVisitor visitor (mMeshes); @@ -130,7 +133,21 @@ namespace MWWorld mPreloadedNodes.push_back(mSceneManager->getTemplate(*it)); mPreloadedShapes.push_back(mBulletShapeManager->getShape(*it)); - // TODO: load .kf + size_t slashpos = mesh.find_last_of("/\\"); + if (slashpos != std::string::npos && slashpos != mesh.size()-1) + { + Misc::StringUtils::lowerCaseInPlace(mesh); + if (mesh[slashpos+1] == 'x') + { + std::string kfname = mesh; + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + { + kfname.replace(kfname.size()-4, 4, ".kf"); + mPreloadedKeyframes.push_back(mKeyframeManager->get(kfname)); + } + + } + } // TODO: do a createInstance() and hold on to it since we can make use of it when the cell goes active } @@ -148,10 +165,12 @@ namespace MWWorld MeshList mMeshes; Resource::SceneManager* mSceneManager; Resource::BulletShapeManager* mBulletShapeManager; + Resource::KeyframeManager* mKeyframeManager; // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state std::vector > mPreloadedNodes; std::vector > mPreloadedShapes; + std::vector > mPreloadedKeyframes; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -205,7 +224,7 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager)); + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager())); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); From b2019d31c72d5dd4a30c9b8b7aa214d6dbdf24da Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 15:55:05 +0100 Subject: [PATCH 218/765] Mark thread safe methods in BsaFile --- components/bsa/bsa_file.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 8ed63f35da..5ff86ef650 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -90,6 +90,7 @@ private: void readHeader(); /// Get the index of a given file name, or -1 if not found + /// @note Thread safe. int getIndex(const char *str) const; public: @@ -116,12 +117,17 @@ public: /** Open a file contained in the archive. Throws an exception if the file doesn't exist. + * @note Thread safe. */ Files::IStreamPtr getFile(const char *file); + /** Open a file contained in the archive. + * @note Thread safe. + */ Files::IStreamPtr getFile(const FileStruct* file); /// Get a list of all files + /// @note Thread safe. const FileList &getList() const { return files; } }; From 84f01b7527c5359ba441e0b3efc1f15c469f0d0c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 16:27:28 +0100 Subject: [PATCH 219/765] Remove unneeded forward declaration --- components/sceneutil/workqueue.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 06489dfbbc..bc2e55647c 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -37,7 +37,6 @@ namespace SceneUtil OpenThreads::Condition mCondition; }; - class WorkQueue; class WorkThread; /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. From effe022bb2a2e5d15cc03324facd11a145d639dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 20:52:32 +0100 Subject: [PATCH 220/765] Move preload model list to MWClass, preload NPC head/hair --- apps/openmw/mwclass/creature.cpp | 23 +++++++++ apps/openmw/mwclass/creature.hpp | 3 ++ apps/openmw/mwclass/npc.cpp | 68 +++++++++++++++++++++++++++ apps/openmw/mwclass/npc.hpp | 3 ++ apps/openmw/mwworld/cellpreloader.cpp | 46 +----------------- apps/openmw/mwworld/class.cpp | 7 +++ apps/openmw/mwworld/class.hpp | 3 ++ 7 files changed, 108 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index e9502d86b8..6e9cfccb96 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -182,6 +182,29 @@ namespace MWClass return ""; } + void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + std::string model = getModel(ptr); + if (!model.empty()) + models.push_back(model); + + // FIXME: use const version of InventoryStore functions once they are available + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) + { + model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } + } + } + } + std::string Creature::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index cb89a53d62..bea56887a0 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -101,6 +101,9 @@ namespace MWClass virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual bool isActor() const { return true; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 57a6d088ab..14bd0640ca 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -429,6 +429,74 @@ namespace MWClass return model; } + void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + const MWWorld::LiveCellRef *npc = ptr.get(); + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); + if(race && race->mData.mFlags & ESM::Race::Beast) + models.push_back("meshes\\base_animkna.nif"); + + // keep these always loaded just in case + models.push_back("meshes/xargonian_swimkna.nif"); + models.push_back("meshes/xbase_anim_female.nif"); + models.push_back("meshes/xbase_anim.nif"); + + if (!npc->mBase->mModel.empty()) + models.push_back("meshes/"+npc->mBase->mModel); + + if (!npc->mBase->mHead.empty()) + { + const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHead); + if (head) + models.push_back("meshes/"+head->mModel); + } + if (!npc->mBase->mHair.empty()) + { + const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHair); + if (hair) + models.push_back("meshes/"+hair->mModel); + } + + // FIXME: use const version of InventoryStore functions once they are available + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) + { + std::vector parts; + if(equipped->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = equipped->get()->mBase; + parts = clothes->mParts.mParts; + } + else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + { + const ESM::Armor *armor = equipped->get()->mBase; + parts = armor->mParts.mParts; + } + else + { + std::string model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } + + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + const std::string& partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mFemale : it->mMale; + const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); + if (part && !part->mModel.empty()) + models.push_back("meshes/"+part->mModel); + } + } + } + } + + } + std::string Npc::getName (const MWWorld::ConstPtr& ptr) const { if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 5df34380a5..95edbd4089 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -75,6 +75,9 @@ namespace MWClass virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 4780582c52..1a8ee27094 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -31,51 +31,7 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - std::string model = ptr.getClass().getModel(ptr); - if (!model.empty()) - mOut.push_back(model); - - // TODO: preload NPC body parts (mHead / mHair) - - // FIXME: use const version of InventoryStore functions once they are available - if (ptr.getClass().hasInventoryStore(ptr)) - { - MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot); - if (equipped != invStore.end()) - { - std::vector parts; - if(equipped->getTypeName() == typeid(ESM::Clothing).name()) - { - const ESM::Clothing *clothes = equipped->get()->mBase; - parts = clothes->mParts.mParts; - } - else if(equipped->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = equipped->get()->mBase; - parts = armor->mParts.mParts; - } - else - { - model = equipped->getClass().getModel(*equipped); - if (!model.empty()) - mOut.push_back(model); - } - - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) - { - const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(it->mMale); - if (part && !part->mModel.empty()) - mOut.push_back("meshes/"+part->mModel); - part = MWBase::Environment::get().getWorld()->getStore().get().search(it->mFemale); - if (part && !part->mModel.empty()) - mOut.push_back("meshes/"+part->mModel); - } - } - } - } + ptr.getClass().getModelsToPreload(ptr, mOut); return true; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 7f2d759b95..ecfe7cb7c4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -291,6 +291,13 @@ namespace MWWorld return ""; } + void Class::getModelsToPreload(const Ptr &ptr, std::vector &models) const + { + std::string model = getModel(ptr); + if (!model.empty()) + models.push_back(model); + } + std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 85cd9ff7c5..0bb3edbeef 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -262,6 +262,9 @@ namespace MWWorld virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. From 84dcf59c50b21604c18ffc62078ea0f5d8499bd5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 22:57:34 +0100 Subject: [PATCH 221/765] Fix preloading of equipment parts that don't separate gender --- apps/openmw/mwclass/npc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 14bd0640ca..5f7f913290 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -486,7 +486,9 @@ namespace MWClass for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { - const std::string& partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mFemale : it->mMale; + std::string partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mFemale : it->mMale; + if (partname.empty()) + partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mMale : it->mFemale; const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); if (part && !part->mModel.empty()) models.push_back("meshes/"+part->mModel); From 1b8e82e92978c53c4e7d13427975cb49ca72fe19 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 00:26:22 +0100 Subject: [PATCH 222/765] Preload NPC body parts --- apps/openmw/mwclass/npc.cpp | 20 ++- apps/openmw/mwrender/npcanimation.cpp | 220 +++++++++++++------------- apps/openmw/mwrender/npcanimation.hpp | 5 + 3 files changed, 136 insertions(+), 109 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5f7f913290..3519f9d83e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -37,6 +37,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/npcanimation.hpp" #include "../mwgui/tooltips.hpp" @@ -457,7 +458,10 @@ namespace MWClass models.push_back("meshes/"+hair->mModel); } + bool female = (npc->mBase->mFlags & ESM::NPC::Female); + // FIXME: use const version of InventoryStore functions once they are available + // preload equipped items if (ptr.getClass().hasInventoryStore(ptr)) { MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); @@ -486,9 +490,9 @@ namespace MWClass for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { - std::string partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mFemale : it->mMale; + std::string partname = female ? it->mFemale : it->mMale; if (partname.empty()) - partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mMale : it->mFemale; + partname = female ? it->mMale : it->mFemale; const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); if (part && !part->mModel.empty()) models.push_back("meshes/"+part->mModel); @@ -497,6 +501,18 @@ namespace MWClass } } + // preload body parts + if (race) + { + const std::vector& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false); + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + const ESM::BodyPart* part = *it; + if (part && !part->mModel.empty()) + models.push_back("meshes/"+part->mModel); + } + } + } std::string Npc::getName (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c8d7c79c5d..026b3a2cf7 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -619,116 +619,10 @@ void NpcAnimation::updateParts() showWeapons(mShowWeapons); showCarriedLeft(mShowCarriedLeft); - // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination - static std::map< std::pair,std::vector > sRaceMapping; - bool isWerewolf = (mNpcType == Type_Werewolf); - int flags = (isWerewolf ? -1 : 0); - if(!mNpc->isMale()) - { - static const int Flag_Female = 1<<0; - flags |= Flag_Female; - } - if(mViewMode == VM_FirstPerson) - { - static const int Flag_FirstPerson = 1<<1; - flags |= Flag_FirstPerson; - } - std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); - std::pair thisCombination = std::make_pair(race, flags); - if (sRaceMapping.find(thisCombination) == sRaceMapping.end()) - { - typedef std::multimap BodyPartMapType; - static BodyPartMapType sBodyPartMap; - if(sBodyPartMap.empty()) - { - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); - } - std::vector &parts = sRaceMapping[thisCombination]; - parts.resize(ESM::PRT_Count, NULL); - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) - { - if(isWerewolf) - break; - const ESM::BodyPart& bodypart = *it; - if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) - continue; - if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) - continue; - - if (!Misc::StringUtils::ciEqual(bodypart.mRace, mNpc->mRace)) - continue; - - bool firstPerson = (bodypart.mId.size() >= 3) - && bodypart.mId[bodypart.mId.size()-3] == '1' - && bodypart.mId[bodypart.mId.size()-2] == 's' - && bodypart.mId[bodypart.mId.size()-1] == 't'; - if(firstPerson != (mViewMode == VM_FirstPerson)) - { - if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || - bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || - bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || - bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) - { - /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - if(!parts[bIt->second]) - parts[bIt->second] = &*it; - ++bIt; - } - } - continue; - } - - if ((!mNpc->isMale()) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) - { - // Allow opposite gender's parts as fallback if parts for our gender are missing - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - if(!parts[bIt->second]) - parts[bIt->second] = &*it; - ++bIt; - } - continue; - } - - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - parts[bIt->second] = &*it; - ++bIt; - } - } - } - - const std::vector &parts = sRaceMapping[thisCombination]; + const std::vector &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { if(mPartPriorities[part] < 1) @@ -1116,6 +1010,118 @@ void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) mHeadAnimationTime->updatePtr(updated); } +// Remember body parts so we only have to search through the store once for each race/gender/viewmode combination +typedef std::map< std::pair,std::vector > RaceMapping; +static RaceMapping sRaceMapping; + +const std::vector& NpcAnimation::getBodyParts(const std::string &race, bool female, bool firstPerson, bool werewolf) +{ + static const int Flag_FirstPerson = 1<<1; + static const int Flag_Female = 1<<0; + + int flags = (werewolf ? -1 : 0); + if(female) + flags |= Flag_Female; + if(firstPerson) + flags |= Flag_FirstPerson; + + RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags)); + if (found != sRaceMapping.end()) + return found->second; + else + { + std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; + + typedef std::multimap BodyPartMapType; + static BodyPartMapType sBodyPartMap; + if(sBodyPartMap.empty()) + { + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); + } + + parts.resize(ESM::PRT_Count, NULL); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + { + if(werewolf) + break; + const ESM::BodyPart& bodypart = *it; + if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + + if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) + continue; + + bool partFirstPerson = (bodypart.mId.size() >= 3) + && bodypart.mId[bodypart.mId.size()-3] == '1' + && bodypart.mId[bodypart.mId.size()-2] == 's' + && bodypart.mId[bodypart.mId.size()-1] == 't'; + if(partFirstPerson != (firstPerson)) + { + if(firstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || + bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) + { + /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + ++bIt; + } + } + continue; + } + + if ((female) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + { + // Allow opposite gender's parts as fallback if parts for our gender are missing + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + ++bIt; + } + continue; + } + + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + parts[bIt->second] = &*it; + ++bIt; + } + } + return parts; + } +} + void NpcAnimation::setAccurateAiming(bool enabled) { mAccurateAiming = enabled; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index c5fc62f9cc..baf9c8c246 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -10,6 +10,7 @@ namespace ESM { struct NPC; + struct BodyPart; } namespace MWRender @@ -150,6 +151,10 @@ public: void setFirstPersonOffset(const osg::Vec3f& offset); virtual void updatePtr(const MWWorld::Ptr& updated); + + /// Get a list of body parts that may be used by an NPC of given race and gender. + /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain NULL body parts. + static const std::vector& getBodyParts(const std::string& raceId, bool female, bool firstperson, bool werewolf); }; } From d16450bff2b934bc9f8053b510b1501348dc99ca Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 00:28:27 +0100 Subject: [PATCH 223/765] Fix correctActorModelPath in preloader not being used --- apps/openmw/mwworld/cellpreloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 1a8ee27094..06216221b0 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -86,8 +86,8 @@ namespace MWWorld //std::cout << "preloading " << mesh << std::endl; - mPreloadedNodes.push_back(mSceneManager->getTemplate(*it)); - mPreloadedShapes.push_back(mBulletShapeManager->getShape(*it)); + mPreloadedNodes.push_back(mSceneManager->getTemplate(mesh)); + mPreloadedShapes.push_back(mBulletShapeManager->getShape(mesh)); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) From f9082502f8a91132fe45ab9d3f632f865aa2a29d Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 01:02:40 +0100 Subject: [PATCH 224/765] Move construction of WorkQueue to RenderingManager --- apps/openmw/mwrender/renderingmanager.cpp | 7 +++++++ apps/openmw/mwrender/renderingmanager.hpp | 9 +++++++++ apps/openmw/mwworld/cellpreloader.cpp | 1 - apps/openmw/mwworld/scene.cpp | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 07b477c436..6ba30f32c8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -131,6 +132,7 @@ namespace MWRender : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) + , mWorkQueue(new SceneUtil::WorkQueue) , mFogDepth(0.f) , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) @@ -231,6 +233,11 @@ namespace MWRender return mResourceSystem; } + SceneUtil::WorkQueue *RenderingManager::getWorkQueue() + { + return mWorkQueue.get(); + } + void RenderingManager::clearCache() { if (mTerrain.get()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index bc5db562c3..8bc3a0a74d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -42,6 +42,11 @@ namespace Fallback class Map; } +namespace SceneUtil +{ + class WorkQueue; +} + namespace MWRender { @@ -65,6 +70,8 @@ namespace MWRender Resource::ResourceSystem* getResourceSystem(); + SceneUtil::WorkQueue* getWorkQueue(); + void clearCache(); double getReferenceTime() const; @@ -190,6 +197,8 @@ namespace MWRender osg::ref_ptr mLightRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mWorkQueue; + osg::ref_ptr mSunLight; std::auto_ptr mPathgrid; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 06216221b0..2591e8c894 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -156,7 +156,6 @@ namespace MWWorld , mBulletShapeManager(bulletShapeManager) , mExpiryDelay(0.0) { - mWorkQueue = new SceneUtil::WorkQueue; } void CellPreloader::preload(CellStore *cell, double timestamp) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 321c1c622e..2856c77e8e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -459,6 +459,7 @@ namespace MWWorld , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); + mPreloader->setWorkQueue(mRendering.getWorkQueue()); float cacheExpiryDelay = Settings::Manager::getFloat("cache expiry delay", "Cells"); rendering.getResourceSystem()->setExpiryDelay(cacheExpiryDelay); From 1cda2bf796fdb27e40accfa11fcc44d9c609e9df Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 01:17:02 +0100 Subject: [PATCH 225/765] Preload sky & water from the main menu --- apps/openmw/engine.cpp | 2 ++ apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 34 +++++++++++++++++++ apps/openmw/mwrender/renderingmanager.hpp | 2 ++ apps/openmw/mwrender/sky.cpp | 40 +++++++++++++++++++++++ apps/openmw/mwrender/sky.hpp | 2 ++ apps/openmw/mwrender/water.cpp | 12 +++++++ apps/openmw/mwrender/water.hpp | 3 ++ apps/openmw/mwworld/worldimp.cpp | 5 +++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 10 files changed, 104 insertions(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d30d0961aa..b3a58d18e5 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -651,6 +651,8 @@ void OMW::Engine::go() } else if (!mSkipMenu) { + mEnvironment.getWorld()->preloadCommonAssets(); + // start in main menu mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); try diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 119ce6b217..946a9a5ddb 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -95,6 +95,8 @@ namespace MWBase virtual ~World() {} + virtual void preloadCommonAssets() = 0; + virtual void startNewGame (bool bypass) = 0; ///< \param bypass Bypass regular game start. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ba30f32c8..8c23a07071 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -127,6 +127,29 @@ namespace MWRender bool mWireframe; }; + class PreloadCommonAssetsWorkItem : public SceneUtil::WorkItem + { + public: + PreloadCommonAssetsWorkItem(Resource::ResourceSystem* resourceSystem) + : mResourceSystem(resourceSystem) + { + } + + virtual void doWork() + { + for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) + mResourceSystem->getSceneManager()->getTemplate(*it); + for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) + mResourceSystem->getImageManager()->getImage(*it); + } + + std::vector mModels; + std::vector mTextures; + + private: + Resource::ResourceSystem* mResourceSystem; + }; + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, const Fallback::Map* fallback, const std::string& resourcePath) : mViewer(viewer) @@ -238,6 +261,17 @@ namespace MWRender return mWorkQueue.get(); } + void RenderingManager::preloadCommonAssets() + { + osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); + mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); + mWater->listAssetsToPreload(workItem->mTextures); + + workItem->mTextures.push_back("textures/_land_default.dds"); + + mWorkQueue->addWorkItem(workItem); + } + void RenderingManager::clearCache() { if (mTerrain.get()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8bc3a0a74d..72f322d2c5 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -72,6 +72,8 @@ namespace MWRender SceneUtil::WorkQueue* getWorkQueue(); + void preloadCommonAssets(); + void clearCache(); double getReferenceTime() const; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0d3bf1a666..dafb2bb4b4 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1694,6 +1694,46 @@ void SkyManager::setWaterHeight(float height) mUnderwaterSwitch->setWaterLevel(height); } +void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) +{ + models.push_back("meshes/sky_atmosphere.nif"); + if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) + models.push_back("meshes/sky_night_02.nif"); + models.push_back("meshes/sky_night_01.nif"); + models.push_back("meshes/sky_clouds_01.nif"); + + models.push_back("meshes\\ashcloud.nif"); + models.push_back("meshes\\blightcloud.nif"); + models.push_back("meshes\\snow.nif"); + models.push_back("meshes\\blizzard.nif"); + + textures.push_back("textures/tx_mooncircle_full_s.dds"); + textures.push_back("textures/tx_mooncircle_full_m.dds"); + + textures.push_back("textures/tx_masser_new.dds"); + textures.push_back("textures/tx_masser_one_wax.dds"); + textures.push_back("textures/tx_masser_half_wax.dds"); + textures.push_back("textures/tx_masser_three_wax.dds"); + textures.push_back("textures/tx_masser_one_wan.dds"); + textures.push_back("textures/tx_masser_half_wan.dds"); + textures.push_back("textures/tx_masser_three_wan.dds"); + textures.push_back("textures/tx_masser_full.dds"); + + textures.push_back("textures/tx_secunda_new.dds"); + textures.push_back("textures/tx_secunda_one_wax.dds"); + textures.push_back("textures/tx_secunda_half_wax.dds"); + textures.push_back("textures/tx_secunda_three_wax.dds"); + textures.push_back("textures/tx_secunda_one_wan.dds"); + textures.push_back("textures/tx_secunda_half_wan.dds"); + textures.push_back("textures/tx_secunda_three_wan.dds"); + textures.push_back("textures/tx_secunda_full.dds"); + + textures.push_back("textures/tx_sun_05.dds"); + textures.push_back("textures/tx_sun_flash_grey_05.dds"); + + textures.push_back("textures/tx_raindrop_01.dds"); +} + void SkyManager::setWaterEnabled(bool enabled) { mUnderwaterSwitch->setEnabled(enabled); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 0caadaa073..741911b238 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -153,6 +153,8 @@ namespace MWRender /// Set height of water plane (used to remove underwater weather particles) void setWaterHeight(float height); + void listAssetsToPreload(std::vector& models, std::vector& textures); + private: void create(); ///< no need to call this, automatically done on first enable() diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 0e4f1a974b..a8d601f114 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -649,6 +649,18 @@ Water::~Water() } } +void Water::listAssetsToPreload(std::vector &textures) +{ + int frameCount = mFallback->getFallbackInt("Water_SurfaceFrameCount"); + std::string texture = mFallback->getFallbackString("Water_SurfaceTexture"); + for (int i=0; i +#include #include #include @@ -85,6 +86,8 @@ namespace MWRender const std::string& resourcePath); ~Water(); + void listAssetsToPreload(std::vector& textures); + void setEnabled(bool enabled); bool toggle(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9064dc94cd..137dac42e8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3216,4 +3216,9 @@ namespace MWWorld return mPhysics->getHitDistance(weaponPos, target); } + void World::preloadCommonAssets() + { + mRendering->preloadCommonAssets(); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8c56443f79..cf9321da52 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -183,6 +183,8 @@ namespace MWWorld virtual void startNewGame (bool bypass); ///< \param bypass Bypass regular game start. + virtual void preloadCommonAssets(); + virtual void clear(); virtual int countSavedGameRecords() const; From 10a3e270a3d32aaa9f34e575f544dfd2f3e4e922 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 01:51:23 +0100 Subject: [PATCH 226/765] Preload fast travel destinations --- apps/openmw/mwworld/scene.cpp | 87 +++++++++++++++++++++++++++++++---- apps/openmw/mwworld/scene.hpp | 3 ++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2856c77e8e..130644d7f0 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -633,6 +633,7 @@ namespace MWWorld { preloadTeleportDoorDestinations(); preloadExteriorGrid(); + preloadFastTravelDestinations(); } void Scene::preloadTeleportDoorDestinations() @@ -664,19 +665,12 @@ namespace MWWorld try { if (!door.getCellRef().getDestCell().empty()) - mPreloader->preload(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()), mRendering.getReferenceTime()); + preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); else { int x,y; MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y); - - for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) - { - for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) - { - mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime()); - } - } + preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); } } catch (std::exception& e) @@ -717,7 +711,80 @@ namespace MWWorld float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance; if (dist < loadDist) - mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy), mRendering.getReferenceTime()); + preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy)); + } + } + } + + void Scene::preloadCell(CellStore *cell, bool preloadSurrounding) + { + if (preloadSurrounding && cell->isExterior()) + { + int x = cell->getCell()->getGridX(); + int y = cell->getCell()->getGridY(); + for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) + { + for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) + { + mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime()); + } + } + } + else + mPreloader->preload(cell, mRendering.getReferenceTime()); + } + + struct ListFastTravelDestinationsVisitor + { + ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) + : mPreloadDist(preloadDist) + , mPlayerPos(playerPos) + { + } + + bool operator()(const MWWorld::Ptr& ptr) + { + if ((ptr.getRefData().getPosition().asVec3() - mPlayerPos).length2() > mPreloadDist * mPreloadDist) + return true; + + if (ptr.getClass().isNpc()) + { + const std::vector& transport = ptr.get()->mBase->mTransport.mList; + mList.insert(mList.begin(), transport.begin(), transport.end()); + } + else + { + const std::vector& transport = ptr.get()->mBase->mTransport.mList; + mList.insert(mList.begin(), transport.begin(), transport.end()); + } + return true; + } + float mPreloadDist; + osg::Vec3f mPlayerPos; + std::vector mList; + }; + + void Scene::preloadFastTravelDestinations() + { + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); + + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); iter!=mActiveCells.end(); ++iter) + { + MWWorld::CellStore* cellStore = *iter; + cellStore->forEachType(listVisitor); + cellStore->forEachType(listVisitor); + } + + for (std::vector::const_iterator it = listVisitor.mList.begin(); it != listVisitor.mList.end(); ++it) + { + if (!it->mCellName.empty()) + preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName)); + else + { + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex( it->mPos.pos[0], it->mPos.pos[1], x, y); + preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); } } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 34e137e45a..f473f7a3c9 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -74,6 +74,9 @@ namespace MWWorld void preloadCells(); void preloadTeleportDoorDestinations(); void preloadExteriorGrid(); + void preloadFastTravelDestinations(); + + void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); public: From 6806741d9b9d1e5e613d667771c018baf4165af2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 01:58:07 +0100 Subject: [PATCH 227/765] Add settings for disabling the individual preloading types --- apps/openmw/mwworld/scene.cpp | 12 +++++++++--- apps/openmw/mwworld/scene.hpp | 4 ++++ files/settings-default.cfg | 11 ++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 130644d7f0..3658bff35d 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -457,6 +457,9 @@ namespace MWWorld , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) + , mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells")) + , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) + , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) { mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); @@ -631,9 +634,12 @@ namespace MWWorld void Scene::preloadCells() { - preloadTeleportDoorDestinations(); - preloadExteriorGrid(); - preloadFastTravelDestinations(); + if (mPreloadDoors) + preloadTeleportDoorDestinations(); + if (mPreloadExteriorGrid) + preloadExteriorGrid(); + if (mPreloadFastTravel) + preloadFastTravelDestinations(); } void Scene::preloadTeleportDoorDestinations() diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index f473f7a3c9..5c429f7c70 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -64,6 +64,10 @@ namespace MWWorld float mPreloadDistance; bool mPreloadEnabled; + bool mPreloadExteriorGrid; + bool mPreloadDoors; + bool mPreloadFastTravel; + void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4df20e7096..4a9a5dd65e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -37,9 +37,18 @@ first person field of view = 55.0 # dramatically affect performance, see documentation for details. exterior cell load distance = 1 -# Preload cells in a background thread +# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled. preload enabled = true +# Preload adjacent cells when moving close to an exterior cell border. +preload exterior grid = true + +# Preload possible fast travel destinations. +preload fast travel = true + +# Preload the locations that doors lead to. +preload doors = true + # Preloading distance threshold preload distance = 1000 From f6f9eff9a68e5ef7297ef97323c5096aab4f490e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 03:06:00 +0100 Subject: [PATCH 228/765] Preload levelled creatures --- apps/openmw/mwclass/creaturelevlist.cpp | 17 +++++++++++++++++ apps/openmw/mwclass/creaturelevlist.hpp | 3 +++ apps/openmw/mwworld/scene.cpp | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index db2ba45bc7..e2f29ea72e 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -6,6 +6,7 @@ #include "../mwmechanics/levelledlist.hpp" #include "../mwworld/customdata.hpp" +#include "../mwmechanics/creaturestats.hpp" namespace MWClass { @@ -53,6 +54,22 @@ namespace MWClass registerClass (typeid (ESM::CreatureLevList).name(), instance); } + void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) + continue; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::ManualRef ref(store, it->mId); + ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); + } + } + void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 67a7858d87..25b5cbddfb 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -17,6 +17,9 @@ namespace MWClass static void registerSelf(); + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3658bff35d..4c843133d6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -197,7 +197,7 @@ namespace MWWorld if (mPreloadEnabled) { mPreloadTimer += duration; - if (mPreloadTimer > 0.5f) + if (mPreloadTimer > 0.25f) { preloadCells(); mPreloadTimer = 0.f; From d11c2864df515afe083628c781b7058f14fe1375 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 15:30:53 +0100 Subject: [PATCH 229/765] Introduce UnrefQueue to handle the deleting of no longer needed objects in the background thread --- apps/openmw/mwrender/objects.cpp | 12 +++++- apps/openmw/mwrender/objects.hpp | 13 +++++-- apps/openmw/mwrender/renderingmanager.cpp | 8 +++- apps/openmw/mwrender/renderingmanager.hpp | 2 + components/CMakeLists.txt | 2 +- components/sceneutil/unrefqueue.cpp | 46 +++++++++++++++++++++++ components/sceneutil/unrefqueue.hpp | 37 ++++++++++++++++++ components/terrain/terraingrid.cpp | 21 ++++++----- components/terrain/terraingrid.hpp | 9 ++--- 9 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 components/sceneutil/unrefqueue.cpp create mode 100644 components/sceneutil/unrefqueue.hpp diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f58ebb9174..516d10b959 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" @@ -85,9 +86,10 @@ namespace namespace MWRender { -Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode) +Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue) : mRootNode(rootNode) , mResourceSystem(resourceSystem) + , mUnrefQueue(unrefQueue) { } @@ -186,7 +188,11 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) delete iter->second; mObjects.erase(iter); + if (mUnrefQueue.get()) + mUnrefQueue->push(ptr.getRefData().getBaseNode()); + ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); + ptr.getRefData().setBaseNode(NULL); return true; } @@ -200,6 +206,8 @@ void Objects::removeCell(const MWWorld::CellStore* store) { if(iter->first.getCell() == store) { + if (mUnrefQueue.get()) + mUnrefQueue->push(iter->second->getObjectRoot()); delete iter->second; mObjects.erase(iter++); } @@ -211,6 +219,8 @@ void Objects::removeCell(const MWWorld::CellStore* store) if(cell != mCellSceneNodes.end()) { cell->second->getParent(0)->removeChild(cell->second); + if (mUnrefQueue.get()) + mUnrefQueue->push(cell->second); mCellSceneNodes.erase(cell); } } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 3d0c92cb43..5b91abea4d 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -30,6 +30,11 @@ namespace MWWorld class CellStore; } +namespace SceneUtil +{ + class UnrefQueue; +} + namespace MWRender{ class Animation; @@ -65,12 +70,14 @@ class Objects{ osg::ref_ptr mRootNode; - void insertBegin(const MWWorld::Ptr& ptr); - Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mUnrefQueue; + + void insertBegin(const MWWorld::Ptr& ptr); + public: - Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode); + Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue); ~Objects(); /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8c23a07071..5a9cd59fc3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -156,6 +157,7 @@ namespace MWRender , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(new SceneUtil::WorkQueue) + , mUnrefQueue(new SceneUtil::UnrefQueue) , mFogDepth(0.f) , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) @@ -176,7 +178,7 @@ namespace MWRender mPathgrid.reset(new Pathgrid(mRootNode)); - mObjects.reset(new Objects(mResourceSystem, lightRoot)); + mObjects.reset(new Objects(mResourceSystem, lightRoot, mUnrefQueue.get())); mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); @@ -187,7 +189,7 @@ namespace MWRender mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain)); + new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); @@ -437,6 +439,8 @@ namespace MWRender void RenderingManager::update(float dt, bool paused) { + mUnrefQueue->flush(mWorkQueue.get()); + if (!paused) { mEffectManager->update(dt); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 72f322d2c5..3d6a4ac3a2 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -45,6 +45,7 @@ namespace Fallback namespace SceneUtil { class WorkQueue; + class UnrefQueue; } namespace MWRender @@ -200,6 +201,7 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; + osg::ref_ptr mUnrefQueue; osg::ref_ptr mSunLight; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 26e8f7b6a3..81e6defe46 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,7 +46,7 @@ add_component_dir (resource add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller - lightmanager lightutil positionattitudetransform workqueue + lightmanager lightutil positionattitudetransform workqueue unrefqueue ) add_component_dir (nif diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp new file mode 100644 index 0000000000..23b1359e58 --- /dev/null +++ b/components/sceneutil/unrefqueue.cpp @@ -0,0 +1,46 @@ +#include "unrefqueue.hpp" + +#include +//#include +//#include + +#include + +namespace SceneUtil +{ + + class UnrefWorkItem : public SceneUtil::WorkItem + { + public: + std::vector > mObjects; + + virtual void doWork() + { + //osg::Timer timer; + //size_t objcount = mObjects.size(); + mObjects.clear(); + //std::cout << "cleared " << objcount << " objects in " << timer.time_m() << std::endl; + } + }; + + UnrefQueue::UnrefQueue() + { + mWorkItem = new UnrefWorkItem; + } + + void UnrefQueue::push(osg::Object *obj) + { + mWorkItem->mObjects.push_back(obj); + } + + void UnrefQueue::flush(SceneUtil::WorkQueue *workQueue) + { + if (mWorkItem->mObjects.empty()) + return; + + workQueue->addWorkItem(mWorkItem); + + mWorkItem = new UnrefWorkItem; + } + +} diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp new file mode 100644 index 0000000000..d0bec061c4 --- /dev/null +++ b/components/sceneutil/unrefqueue.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H +#define OPENMW_COMPONENTS_UNREFQUEUE_H + +#include +#include + +namespace osg +{ + class Object; +} + +namespace SceneUtil +{ + class WorkQueue; + class UnrefWorkItem; + + /// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario + /// would be the main thread pushing objects that are no longer needed, and the background thread deleting them. + class UnrefQueue : public osg::Referenced + { + public: + UnrefQueue(); + + /// Adds an object to the list of objects to be unreferenced. Call from the main thread. + void push(osg::Object* obj); + + /// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, thus unreferencing them. + /// Call from the main thread. + void flush(SceneUtil::WorkQueue* workQueue); + + private: + osg::ref_ptr mWorkItem; + }; + +} + +#endif diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4f7a0772b7..4b67371e08 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -46,11 +47,10 @@ namespace namespace Terrain { -TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask) +TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) - , mKdTreeBuilder(new osg::KdTreeBuilder) + , mUnrefQueue(unrefQueue) { mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } @@ -212,13 +212,6 @@ void TerrainGrid::loadCell(int x, int y) element->mNode = terrainNode; mTerrainRoot->addChild(element->mNode); - // kdtree probably not needed with mNumSplits=4 - /* - // build a kdtree to speed up intersection tests with the terrain - // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree - geode->accept(*mKdTreeBuilder); - */ - mGrid[std::make_pair(x,y)] = element.release(); } @@ -230,6 +223,10 @@ void TerrainGrid::unloadCell(int x, int y) GridElement* element = it->second; mTerrainRoot->removeChild(element->mNode); + + if (mUnrefQueue.get()) + mUnrefQueue->push(element->mNode); + delete element; mGrid.erase(it); @@ -240,7 +237,11 @@ void TerrainGrid::clearCache() for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { if (it->second->referenceCount() <= 1) + { + if (mUnrefQueue.get()) + mUnrefQueue->push(it->second); mTextureCache.erase(it++); + } else ++it; } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 81bfc4211e..5708e70685 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -6,9 +6,9 @@ #include "world.hpp" #include "material.hpp" -namespace osg +namespace SceneUtil { - class KdTreeBuilder; + class UnrefQueue; } namespace Terrain @@ -20,8 +20,7 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask); + TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); virtual void loadCell(int x, int y); @@ -42,7 +41,7 @@ namespace Terrain typedef std::map, GridElement*> Grid; Grid mGrid; - osg::ref_ptr mKdTreeBuilder; + osg::ref_ptr mUnrefQueue; }; } From 40a6e05e178b28ddbdf8c909f7aab2dce7fa9af0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 16:09:55 +0100 Subject: [PATCH 230/765] Use a deque instead of vector in UnrefQueue --- components/sceneutil/unrefqueue.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp index 23b1359e58..0b65c4a326 100644 --- a/components/sceneutil/unrefqueue.cpp +++ b/components/sceneutil/unrefqueue.cpp @@ -1,5 +1,7 @@ #include "unrefqueue.hpp" +#include + #include //#include //#include @@ -12,7 +14,7 @@ namespace SceneUtil class UnrefWorkItem : public SceneUtil::WorkItem { public: - std::vector > mObjects; + std::deque > mObjects; virtual void doWork() { From ae031b23d42d7174f5c1187d79e1e47040d1e9df Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 16:10:42 +0100 Subject: [PATCH 231/765] Do not detach NPC parts in destructor --- apps/openmw/mwrender/animation.cpp | 7 ++++++- apps/openmw/mwrender/animation.hpp | 3 +++ apps/openmw/mwrender/npcanimation.cpp | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b7c0c0a365..3d4fd11d05 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1409,8 +1409,13 @@ namespace MWRender PartHolder::~PartHolder() { - if (mNode->getNumParents()) + if (mNode.get() && mNode->getNumParents()) mNode->getParent(0)->removeChild(mNode); } + void PartHolder::unlink() + { + mNode = NULL; + } + } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 992462e1f1..feaf0e16c3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -55,6 +55,9 @@ public: ~PartHolder(); + /// Unreferences mNode *without* detaching it from the graph. Only use if you know what you are doing. + void unlink(); + osg::ref_ptr getNode() { return mNode; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 026b3a2cf7..10c0032d40 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -273,6 +273,13 @@ NpcAnimation::~NpcAnimation() // all from within this destructor. ouch! && mPtr.getRefData().getCustomData() && mPtr.getClass().getInventoryStore(mPtr).getListener() == this) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); + + // do not detach (delete) parts yet, this is done so the background thread can handle the deletion + for(size_t i = 0;i < ESM::PRT_Count;i++) + { + if (mObjectParts[i].get()) + mObjectParts[i]->unlink(); + } } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, From 8ece1885cdc2662ceaefcd4542abdf9970880483 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 16:18:19 +0100 Subject: [PATCH 232/765] Animation: don't create the NodeMap if we don't need it --- apps/openmw/mwrender/animation.cpp | 61 +++++++++++++++++------------- apps/openmw/mwrender/animation.hpp | 5 ++- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3d4fd11d05..5aea13660f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -95,7 +95,12 @@ namespace class NodeMapVisitor : public osg::NodeVisitor { public: - NodeMapVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} + typedef std::map > NodeMap; + + NodeMapVisitor(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + {} void apply(osg::MatrixTransform& trans) { @@ -103,15 +108,8 @@ namespace traverse(trans); } - typedef std::map > NodeMap; - - const NodeMap& getNodeMap() const - { - return mMap; - } - private: - NodeMap mMap; + NodeMap& mMap; }; NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) @@ -312,6 +310,7 @@ namespace MWRender Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) , mSkeleton(NULL) + , mNodeMapCreated(false) , mPtr(ptr) , mResourceSystem(resourceSystem) , mAccumulate(1.f, 1.f, 0.f) @@ -407,12 +406,14 @@ namespace MWRender if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) return; + const NodeMap& nodeMap = getNodeMap(); + for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); - NodeMap::const_iterator found = mNodeMap.find(bonename); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = nodeMap.find(bonename); + if (found == nodeMap.end()) { std::cerr << "addAnimSource: can't find bone '" + bonename << "' in " << model << " (referenced by " << kfname << ")" << std::endl; continue; @@ -436,11 +437,11 @@ namespace MWRender if (!mAccumRoot) { - NodeMap::const_iterator found = mNodeMap.find("root bone"); - if (found == mNodeMap.end()) - found = mNodeMap.find("bip01"); + NodeMap::const_iterator found = nodeMap.find("root bone"); + if (found == nodeMap.end()) + found = nodeMap.find("bip01"); - if (found != mNodeMap.end()) + if (found != nodeMap.end()) mAccumRoot = found->second; } } @@ -690,6 +691,17 @@ namespace MWRender mTextKeyListener = listener; } + const Animation::NodeMap &Animation::getNodeMap() const + { + if (!mNodeMapCreated && mObjectRoot) + { + NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + mNodeMapCreated = true; + } + return mNodeMap; + } + void Animation::resetActiveGroups() { // remove all previous external controllers from the scene graph @@ -729,7 +741,7 @@ namespace MWRender for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { - osg::ref_ptr node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource + osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource node->addUpdateCallback(it->second); mActiveControllers.insert(std::make_pair(node, it->second)); @@ -978,6 +990,7 @@ namespace MWRender mSkeleton = NULL; mNodeMap.clear(); + mNodeMapCreated = false; mActiveControllers.clear(); mAccumRoot = NULL; mAccumCtrl = NULL; @@ -1025,10 +1038,6 @@ namespace MWRender removeTriBipVisitor.remove(); } - NodeMapVisitor visitor; - mObjectRoot->accept(visitor); - mNodeMap = visitor.getNodeMap(); - mObjectRoot->addCullCallback(new SceneUtil::LightListCallback); } @@ -1121,8 +1130,8 @@ namespace MWRender parentNode = mInsert; else { - NodeMap::iterator found = mNodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + if (found == getNodeMap().end()) throw std::runtime_error("Can't find bone " + bonename); parentNode = found->second; @@ -1222,8 +1231,8 @@ namespace MWRender const osg::Node* Animation::getNode(const std::string &name) const { std::string lowerName = Misc::StringUtils::lowerCase(name); - NodeMap::const_iterator found = mNodeMap.find(lowerName); - if (found == mNodeMap.end()) + NodeMap::const_iterator found = getNodeMap().find(lowerName); + if (found == getNodeMap().end()) return NULL; else return found->second; @@ -1317,8 +1326,8 @@ namespace MWRender if (mPtr.getClass().isBipedal(mPtr)) { - NodeMap::iterator found = mNodeMap.find("bip01 head"); - if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + NodeMap::const_iterator found = getNodeMap().find("bip01 head"); + if (found != getNodeMap().end() && dynamic_cast(found->second.get())) { osg::Node* node = found->second; mHeadController = new RotateController(mObjectRoot.get()); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index feaf0e16c3..1d7930566a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -232,7 +232,8 @@ protected: // Stored in all lowercase for a case-insensitive lookup typedef std::map > NodeMap; - NodeMap mNodeMap; + mutable NodeMap mNodeMap; + mutable bool mNodeMapCreated; MWWorld::Ptr mPtr; @@ -263,6 +264,8 @@ protected: float mAlpha; + const NodeMap& getNodeMap() const; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); From ce3cce24a56df230616f13622deb59464b4b36d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 16:20:17 +0100 Subject: [PATCH 233/765] Remove unneeded dynamic_cast --- apps/openmw/mwrender/animation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5aea13660f..c86878e3d7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1327,9 +1327,9 @@ namespace MWRender if (mPtr.getClass().isBipedal(mPtr)) { NodeMap::const_iterator found = getNodeMap().find("bip01 head"); - if (found != getNodeMap().end() && dynamic_cast(found->second.get())) + if (found != getNodeMap().end()) { - osg::Node* node = found->second; + osg::MatrixTransform* node = found->second; mHeadController = new RotateController(mObjectRoot.get()); node->addUpdateCallback(mHeadController); mActiveControllers.insert(std::make_pair(node, mHeadController)); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 10c0032d40..709653cd8e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -847,9 +847,9 @@ void NpcAnimation::addControllers() if (mViewMode == VM_FirstPerson) { NodeMap::iterator found = mNodeMap.find("bip01 neck"); - if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + if (found != mNodeMap.end()) { - osg::Node* node = found->second; + osg::MatrixTransform* node = found->second.get(); mFirstPersonNeckController = new NeckController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); From 2e62298bd33534a64e7eff91c94ea1aac5f806ae Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 18:11:07 +0100 Subject: [PATCH 234/765] Clean up ObjectCache includes --- components/resource/niffilemanager.cpp | 2 ++ components/resource/objectcache.cpp | 2 ++ components/resource/objectcache.hpp | 13 +++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index ecb63db600..82768cc2c5 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -1,5 +1,7 @@ #include "niffilemanager.hpp" +#include + #include #include "objectcache.hpp" diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 17368f34f5..a0a3cf3a52 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -13,6 +13,8 @@ #include "objectcache.hpp" +#include + namespace Resource { diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index b933933f3f..79ebabd5b8 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -17,13 +17,18 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE -#include - -#include -#include +#include +#include +#include #include +namespace osg +{ + class Object; + class State; +} + namespace Resource { class ObjectCache : public osg::Referenced From e28dc3e72fea00ebde993cdb5cc3ea01e84d1b7c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 18:33:02 +0100 Subject: [PATCH 235/765] Preload instances in SceneManager --- apps/opencs/view/render/object.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 6 +- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/effectmanager.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 12 ++-- apps/openmw/mwrender/weaponanimation.cpp | 2 +- apps/openmw/mwworld/cellpreloader.cpp | 9 ++- apps/openmw/mwworld/cellpreloader.hpp | 2 + apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 2 + components/CMakeLists.txt | 2 +- components/resource/multiobjectcache.cpp | 79 ++++++++++++++++++++++ components/resource/multiobjectcache.hpp | 47 +++++++++++++ components/resource/scenemanager.cpp | 39 +++++++++-- components/resource/scenemanager.hpp | 24 +++++-- 16 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 components/resource/multiobjectcache.cpp create mode 100644 components/resource/multiobjectcache.hpp diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index b980b658a4..33939625d4 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -100,7 +100,7 @@ void CSVRender::Object::update() { std::string path = "meshes\\" + model; - mResourceSystem->getSceneManager()->createInstance(path, mBaseNode); + mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } catch (std::exception& e) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index c86878e3d7..398fe53225 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -997,7 +997,7 @@ namespace MWRender if (!forceskeleton) { - osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model, mInsert); + osg::ref_ptr created = mResourceSystem->getSceneManager()->getInstance(model, mInsert); mObjectRoot = created->asGroup(); if (!mObjectRoot) { @@ -1009,7 +1009,7 @@ namespace MWRender } else { - osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr created = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1136,7 +1136,7 @@ namespace MWRender parentNode = found->second; } - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model, parentNode); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, parentNode); node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7c447182f2..9cd35ecde6 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -105,7 +105,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) else bonename = "Shield Bone"; - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(item.getClass().getModel(item)); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, bonename); mResourceSystem->getSceneManager()->notifyAttached(attached); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index c4e457a1fd..e2773f2dc8 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -27,7 +27,7 @@ EffectManager::~EffectManager() void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale) { - osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); node->setNodeMask(Mask_Effect); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 709653cd8e..eee23aa312 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -647,7 +647,7 @@ void NpcAnimation::updateParts() PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { - osg::ref_ptr instance = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename); mResourceSystem->getSceneManager()->notifyAttached(attached); if (enchantedGlow) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dafb2bb4b4..40283ba275 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1146,7 +1146,7 @@ void SkyManager::create() { assert(!mCreated); - mAtmosphereDay = mSceneManager->createInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); + mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(0); mAtmosphereDay->accept(modAtmosphere); @@ -1159,9 +1159,9 @@ void SkyManager::create() osg::ref_ptr atmosphereNight; if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) - atmosphereNight = mSceneManager->createInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); else - atmosphereNight = mSceneManager->createInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); ModVertexAlphaVisitor modStars(2); atmosphereNight->accept(modStars); @@ -1176,14 +1176,14 @@ void SkyManager::create() mCloudNode = new osg::PositionAttitudeTransform; mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); mCloudUpdater = new CloudUpdater; mCloudUpdater->setOpacity(1.f); mCloudMesh->addUpdateCallback(mCloudUpdater); - mCloudMesh2 = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); mCloudMesh2->accept(modClouds); mCloudUpdater2 = new CloudUpdater; mCloudUpdater2->setOpacity(0.f); @@ -1537,7 +1537,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mParticleNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } - mParticleEffect = mSceneManager->createInstance(mCurrentParticleEffect, mParticleNode); + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 4328d5a3d7..8fd294ccda 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -84,7 +84,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; std::string model = ammo->getClass().getModel(*ammo); - osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->createInstance(model, parent); + osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->getInstance(model, parent); mAmmunition = PartHolderPtr(new PartHolder(arrow)); } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 2591e8c894..c6fa3b949a 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -86,7 +86,7 @@ namespace MWWorld //std::cout << "preloading " << mesh << std::endl; - mPreloadedNodes.push_back(mSceneManager->getTemplate(mesh)); + mPreloadedNodes.push_back(mSceneManager->cacheInstance(mesh)); mPreloadedShapes.push_back(mBulletShapeManager->getShape(mesh)); size_t slashpos = mesh.find_last_of("/\\"); @@ -104,8 +104,6 @@ namespace MWWorld } } - - // TODO: do a createInstance() and hold on to it since we can make use of it when the cell goes active } catch (std::exception& e) { @@ -185,6 +183,11 @@ namespace MWWorld mPreloadCells[cell] = PreloadEntry(timestamp, item); } + void CellPreloader::notifyLoaded(CellStore *cell) + { + mPreloadCells.erase(cell); + } + void CellPreloader::updateCache(double timestamp) { // TODO: add settings for a minimum/maximum size of the cache diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index c7495182b3..55e40dcbd1 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -24,6 +24,8 @@ namespace MWWorld /// @note The cell itself must be in State_Loaded or State_Preloaded. void preload(MWWorld::CellStore* cell, double timestamp); + void notifyLoaded(MWWorld::CellStore* cell); + /// Removes preloaded cells that have not had a preload request for a while. void updateCache(double timestamp); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d1faf621c2..b8150ce9c9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -92,7 +92,7 @@ namespace MWWorld attachTo = rotateNode; } - mResourceSystem->getSceneManager()->createInstance(model, attachTo); + mResourceSystem->getSceneManager()->getInstance(model, attachTo); SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; state.mNode->accept(disableFreezeOnCullVisitor); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 4c843133d6..46d75daaf5 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -296,6 +296,8 @@ namespace MWWorld if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); } + + mPreloader->notifyLoaded(cell); } void Scene::changeToVoid() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 81e6defe46..0ac32f65ec 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,7 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache resourcesystem resourcemanager + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager ) add_component_dir (sceneutil diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp new file mode 100644 index 0000000000..352715f19f --- /dev/null +++ b/components/resource/multiobjectcache.cpp @@ -0,0 +1,79 @@ +#include "multiobjectcache.hpp" + +#include + +#include + +namespace Resource +{ + + MultiObjectCache::MultiObjectCache() + { + + } + + MultiObjectCache::~MultiObjectCache() + { + + } + + void MultiObjectCache::removeUnreferencedObjectsInCache() + { + std::vector > objectsToRemove; + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove unreferenced entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) + { + if (oitr->second->referenceCount() <= 1) + { + objectsToRemove.push_back(oitr->second); + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } + } + } + + // note, actual unref happens outside of the lock + objectsToRemove.clear(); + } + + void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.insert(std::make_pair(filename, object)); + } + + osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string &fileName) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator found = _objectCache.find(fileName); + if (found == _objectCache.end()) + return osg::ref_ptr(); + else + { + osg::ref_ptr object = found->second; + _objectCache.erase(found); + return object; + } + } + + void MultiObjectCache::releaseGLObjects(osg::State *state) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.get(); + object->releaseGLObjects(state); + } + } + +} diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp new file mode 100644 index 0000000000..b677100f08 --- /dev/null +++ b/components/resource/multiobjectcache.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_COMPONENTS_MULTIOBJECTCACHE_H +#define OPENMW_COMPONENTS_MULTIOBJECTCACHE_H + +#include +#include + +#include +#include + +namespace osg +{ + class Object; + class State; +} + +namespace Resource +{ + + /// @brief Cache for "non reusable" objects. + class MultiObjectCache : public osg::Referenced + { + public: + MultiObjectCache(); + ~MultiObjectCache(); + + void removeUnreferencedObjectsInCache(); + + void addEntryToObjectCache(const std::string& filename, osg::Object* object); + + /** Take an Object from cache. Return NULL if no object found. */ + osg::ref_ptr takeFromObjectCache(const std::string& fileName); + + /** call releaseGLObjects on all objects attached to the object cache.*/ + void releaseGLObjects(osg::State* state); + + protected: + + typedef std::multimap > ObjectCacheMap; + + ObjectCacheMap _objectCache; + OpenThreads::Mutex _objectCacheMutex; + + }; + +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f083e8175d..5acd5f8fb9 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -28,6 +28,7 @@ #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" +#include "multiobjectcache.hpp" namespace { @@ -233,6 +234,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) + , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) @@ -246,7 +248,6 @@ namespace Resource SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types - } /// @brief Callback to read image files from the VFS. @@ -372,7 +373,17 @@ namespace Resource } } - osg::ref_ptr SceneManager::createInstance(const std::string &name) + osg::ref_ptr SceneManager::cacheInstance(const std::string &name) + { + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr node = createInstance(normalized); + mInstanceCache->addEntryToObjectCache(normalized, node.get()); + return node; + } + + osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); osg::ref_ptr cloned = osg::clone(scene.get(), SceneUtil::CopyOp()); @@ -383,9 +394,22 @@ namespace Resource return cloned; } - osg::ref_ptr SceneManager::createInstance(const std::string &name, osg::Group* parentNode) + osg::ref_ptr SceneManager::getInstance(const std::string &name) { - osg::ref_ptr cloned = createInstance(name); + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); + if (obj.get()) + return static_cast(obj.get()); + + return createInstance(normalized); + + } + + osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) + { + osg::ref_ptr cloned = getInstance(name); attachTo(cloned, parentNode); return cloned; } @@ -487,4 +511,11 @@ namespace Resource mUnRefImageDataAfterApply = unref; } + void SceneManager::updateCache(double referenceTime) + { + ResourceManager::updateCache(referenceTime); + + mInstanceCache->removeUnreferencedObjectsInCache(); + } + } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 0345fff228..8357e63cd3 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -29,6 +29,8 @@ namespace osgViewer namespace Resource { + class MultiObjectCache; + /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. class SceneManager : public ResourceManager @@ -43,15 +45,21 @@ namespace Resource /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name); - /// Create an instance of the given scene template + /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply + /// return this cached object instead of creating a new one. + /// @note The returned ref_ptr may be kept around by the caller to ensure that the object stays in cache for as long as needed. + /// @note Thread safe. + osg::ref_ptr cacheInstance(const std::string& name); + + /// Get an instance of the given scene template /// @see getTemplate /// @note Thread safe. - osg::ref_ptr createInstance(const std::string& name); + osg::ref_ptr getInstance(const std::string& name); - /// Create an instance of the given scene template and immediately attach it to a parent node + /// Get an instance of the given scene template and immediately attach it to a parent node /// @see getTemplate /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. - osg::ref_ptr createInstance(const std::string& name, osg::Group* parentNode); + osg::ref_ptr getInstance(const std::string& name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node /// @note You should have the parentNode in its intended position before calling this method, @@ -88,7 +96,15 @@ namespace Resource /// otherwise should be disabled to reduce memory usage. void setUnRefImageDataAfterApply(bool unref); + /// @see ResourceManager::updateCache + virtual void updateCache(double referenceTime); + private: + + osg::ref_ptr createInstance(const std::string& name); + + osg::ref_ptr mInstanceCache; + OpenThreads::Mutex mSharedStateMutex; Resource::ImageManager* mImageManager; From 246566cef4294062e8bc636458e4f54fffab56e4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 18:48:49 +0100 Subject: [PATCH 236/765] Preload instances in BulletShapeManager --- apps/openmw/mwphysics/physicssystem.cpp | 4 +-- apps/openmw/mwworld/cellpreloader.cpp | 4 +-- components/resource/bulletshapemanager.cpp | 32 +++++++++++++++++++++- components/resource/bulletshapemanager.hpp | 18 ++++++++++-- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6417968d3a..5d0ac1f8fc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1088,7 +1088,7 @@ namespace MWPhysics void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; @@ -1234,7 +1234,7 @@ namespace MWPhysics } void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { - osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance) return; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index c6fa3b949a..047c1e99b9 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -87,7 +87,7 @@ namespace MWWorld //std::cout << "preloading " << mesh << std::endl; mPreloadedNodes.push_back(mSceneManager->cacheInstance(mesh)); - mPreloadedShapes.push_back(mBulletShapeManager->getShape(mesh)); + mPreloadedShapes.push_back(mBulletShapeManager->cacheInstance(mesh)); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) @@ -123,7 +123,7 @@ namespace MWWorld // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state std::vector > mPreloadedNodes; - std::vector > mPreloadedShapes; + std::vector > mPreloadedShapes; std::vector > mPreloadedKeyframes; }; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index cb57d686ff..111808e6ed 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -14,7 +14,7 @@ #include "scenemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" - +#include "multiobjectcache.hpp" namespace Resource { @@ -98,6 +98,7 @@ private: BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) : ResourceManager(vfs) + , mInstanceCache(new MultiObjectCache) , mSceneManager(sceneMgr) , mNifFileManager(nifFileManager) { @@ -151,6 +152,28 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & return shape; } +osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr instance = createInstance(normalized); + mInstanceCache->addEntryToObjectCache(normalized, instance.get()); + return instance; +} + +osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); + if (obj.get()) + return static_cast(obj.get()); + else + return createInstance(normalized); +} + osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) { osg::ref_ptr shape = getShape(name); @@ -160,4 +183,11 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: return osg::ref_ptr(); } +void BulletShapeManager::updateCache(double referenceTime) +{ + ResourceManager::updateCache(referenceTime); + + mInstanceCache->removeUnreferencedObjectsInCache(); +} + } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 4c0cb1fd29..14b26962ba 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -17,6 +17,8 @@ namespace Resource class BulletShape; class BulletShapeInstance; + class MultiObjectCache; + /// Handles loading, caching and "instancing" of bullet shapes. /// A shape 'instance' is a clone of another shape, with the goal of setting a different scale on this instance. /// @note May be used from any thread. @@ -26,12 +28,24 @@ namespace Resource BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); ~BulletShapeManager(); + /// @note May return a null pointer if the object has no shape. osg::ref_ptr getShape(const std::string& name); - /// Shorthand for getShape(name)->makeInstance(); - osg::ref_ptr createInstance(const std::string& name); + /// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can simply return + /// the cached instance instead of having to create a new one. + /// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long as needed. + osg::ref_ptr cacheInstance(const std::string& name); + + /// @note May return a null pointer if the object has no shape. + osg::ref_ptr getInstance(const std::string& name); + + /// @see ResourceManager::updateCache + virtual void updateCache(double referenceTime); private: + osg::ref_ptr createInstance(const std::string& name); + + osg::ref_ptr mInstanceCache; SceneManager* mSceneManager; NifFileManager* mNifFileManager; }; From 3552b3a82cf706c8ba63a663a8d9144d90f7505c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 18:51:17 +0100 Subject: [PATCH 237/765] Don't create a BulletShapeInstance for actors --- apps/openmw/mwphysics/actor.cpp | 2 +- apps/openmw/mwphysics/actor.hpp | 4 ++-- apps/openmw/mwphysics/physicssystem.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index cc46897d13..c99754b5cd 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -16,7 +16,7 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) +Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false) , mInternalCollisionMode(true) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 03193c675e..5755def740 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -15,7 +15,7 @@ class btCollisionObject; namespace Resource { - class BulletShapeInstance; + class BulletShape; } namespace MWPhysics @@ -48,7 +48,7 @@ namespace MWPhysics class Actor : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); + Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); ~Actor(); /** diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5d0ac1f8fc..48b3426b82 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1234,11 +1234,11 @@ namespace MWPhysics } void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { - osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); - if (!shapeInstance) + osg::ref_ptr shape = mShapeManager->getShape(mesh); + if (!shape) return; - Actor* actor = new Actor(ptr, shapeInstance, mCollisionWorld); + Actor* actor = new Actor(ptr, shape, mCollisionWorld); mActors.insert(std::make_pair(ptr, actor)); } From afe533e6709496131cd5f424cab3f9b1b538fc25 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 19:00:30 +0100 Subject: [PATCH 238/765] Accept a const Object in UnrefQueue --- components/sceneutil/unrefqueue.cpp | 4 ++-- components/sceneutil/unrefqueue.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp index 0b65c4a326..a5eb1654e6 100644 --- a/components/sceneutil/unrefqueue.cpp +++ b/components/sceneutil/unrefqueue.cpp @@ -14,7 +14,7 @@ namespace SceneUtil class UnrefWorkItem : public SceneUtil::WorkItem { public: - std::deque > mObjects; + std::deque > mObjects; virtual void doWork() { @@ -30,7 +30,7 @@ namespace SceneUtil mWorkItem = new UnrefWorkItem; } - void UnrefQueue::push(osg::Object *obj) + void UnrefQueue::push(const osg::Object *obj) { mWorkItem->mObjects.push_back(obj); } diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp index d0bec061c4..85b1d31e1d 100644 --- a/components/sceneutil/unrefqueue.hpp +++ b/components/sceneutil/unrefqueue.hpp @@ -22,7 +22,7 @@ namespace SceneUtil UnrefQueue(); /// Adds an object to the list of objects to be unreferenced. Call from the main thread. - void push(osg::Object* obj); + void push(const osg::Object* obj); /// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, thus unreferencing them. /// Call from the main thread. From 1457a0de78464a68e9b56e0ccf33189f5e0afc33 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 19:04:59 +0100 Subject: [PATCH 239/765] Use the UnrefQueue to delete BulletShapeInstances --- apps/openmw/mwphysics/physicssystem.cpp | 14 ++++++++++++++ apps/openmw/mwphysics/physicssystem.hpp | 9 +++++++-- apps/openmw/mwrender/renderingmanager.cpp | 5 +++++ apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwworld/scene.cpp | 2 ++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 48b3426b82..9f1cfc6822 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -22,6 +22,7 @@ #include #include +#include #include // FindRecIndexVisitor @@ -533,6 +534,11 @@ namespace MWPhysics setOrigin(btVector3(pos[0], pos[1], pos[2])); } + const Resource::BulletShapeInstance* getShapeInstance() const + { + return mShapeInstance.get(); + } + void setScale(float scale) { mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); @@ -690,6 +696,11 @@ namespace MWPhysics delete mBroadphase; } + void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) + { + mUnrefQueue = unrefQueue; + } + Resource::BulletShapeManager *PhysicsSystem::getShapeManager() { return mShapeManager.get(); @@ -1109,6 +1120,9 @@ namespace MWPhysics { mCollisionWorld->removeCollisionObject(found->second->getCollisionObject()); + if (mUnrefQueue.get()) + mUnrefQueue->push(found->second->getShapeInstance()); + mAnimatedObjects.erase(found->second); delete found->second; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index a866717c76..110b59268f 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -25,11 +25,12 @@ namespace MWRender namespace Resource { class BulletShapeManager; + class ResourceSystem; } -namespace Resource +namespace SceneUtil { - class ResourceSystem; + class UnrefQueue; } class btCollisionWorld; @@ -53,6 +54,8 @@ namespace MWPhysics PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); ~PhysicsSystem (); + void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); + Resource::BulletShapeManager* getShapeManager(); void enableWater(float height); @@ -165,6 +168,8 @@ namespace MWPhysics void updateWater(); + osg::ref_ptr mUnrefQueue; + btBroadphaseInterface* mBroadphase; btDefaultCollisionConfiguration* mCollisionConfiguration; btCollisionDispatcher* mDispatcher; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5a9cd59fc3..be3caaeb43 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -263,6 +263,11 @@ namespace MWRender return mWorkQueue.get(); } + SceneUtil::UnrefQueue *RenderingManager::getUnrefQueue() + { + return mUnrefQueue.get(); + } + void RenderingManager::preloadCommonAssets() { osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 3d6a4ac3a2..4c850de126 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -72,6 +72,7 @@ namespace MWRender Resource::ResourceSystem* getResourceSystem(); SceneUtil::WorkQueue* getWorkQueue(); + SceneUtil::UnrefQueue* getUnrefQueue(); void preloadCommonAssets(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 46d75daaf5..b0e3844934 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -466,6 +466,8 @@ namespace MWWorld mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); + mPhysics->setUnrefQueue(rendering.getUnrefQueue()); + float cacheExpiryDelay = Settings::Manager::getFloat("cache expiry delay", "Cells"); rendering.getResourceSystem()->setExpiryDelay(cacheExpiryDelay); mPreloader->setExpiryDelay(cacheExpiryDelay); From 596fe56bfdfe95dbc1a8783f65bf33c73694cd7e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 20:14:16 +0100 Subject: [PATCH 240/765] Make Land::loadData thread safe --- apps/opencs/model/tools/mergestages.cpp | 2 -- apps/openmw/mwrender/terrainstorage.cpp | 3 ++ components/esm/loadland.cpp | 42 ++++++++++++++----------- components/esm/loadland.hpp | 7 +++-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 52e1e69649..f52e8886f0 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -224,8 +224,6 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::Land newLand (land); - newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, - // because record is already fully loaded) newLand.mPlugin = 0; if (land.mDataTypes & ESM::Land::DATA_VTEX) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index ed1d8b6779..02947b5dd3 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -62,6 +62,9 @@ namespace MWRender const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; if (!land->isDataLoaded(flags)) land->loadData(flags); + + // TODO: unload land data when it's no longer needed + return land; } diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index e7be033219..b0b072232a 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,6 +2,8 @@ #include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -60,7 +62,6 @@ namespace ESM , mX(0) , mY(0) , mPlugin(0) - , mEsm(NULL) , mDataTypes(0) , mDataLoaded(false) , mLandData(NULL) @@ -86,8 +87,7 @@ namespace ESM { isDeleted = false; - mEsm = &esm; - mPlugin = mEsm->getIndex(); + mPlugin = esm.getIndex(); bool hasLocation = false; bool isLoaded = false; @@ -180,6 +180,8 @@ namespace ESM void Land::loadData(int flags) const { + OpenThreads::ScopedLock lock(mMutex); + // Try to load only available data flags = flags & mDataTypes; // Return if all required data is loaded @@ -191,15 +193,17 @@ namespace ESM mLandData = new LandData; mLandData->mDataTypes = mDataTypes; } - mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + ESM::ESMReader reader; + reader.restoreContext(mContext); + + if (reader.isNextSub("VNML")) { + condLoad(reader, flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } - if (mEsm->isNextSub("VHGT")) { + if (reader.isNextSub("VHGT")) { static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + if (condLoad(reader, flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; @@ -217,14 +221,14 @@ namespace ESM } } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + if (reader.isNextSub("WNAM")) { + condLoad(reader, flags, DATA_WNAM, mLandData->mWnam, 81); } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { + if (reader.isNextSub("VCLR")) + condLoad(reader, flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (reader.isNextSub("VTEX")) { static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + if (condLoad(reader, flags, DATA_VTEX, vtex, sizeof(vtex))) { LandData::transposeTextureData(vtex, mLandData->mTextures); } } @@ -240,25 +244,26 @@ namespace ESM } } - bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const + bool Land::condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); + reader.getHExact(ptr, size); mDataLoaded |= dataFlag; return true; } - mEsm->skipHSubSize(size); + reader.skipHSubSize(size); return false; } bool Land::isDataLoaded(int flags) const { + OpenThreads::ScopedLock lock(mMutex); return (mDataLoaded & flags) == (flags & mDataTypes); } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), - mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mContext (land.mContext), mDataTypes (land.mDataTypes), mDataLoaded (land.mDataLoaded), mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) {} @@ -275,7 +280,6 @@ namespace ESM std::swap (mX, land.mX); std::swap (mY, land.mY); std::swap (mPlugin, land.mPlugin); - std::swap (mEsm, land.mEsm); std::swap (mContext, land.mContext); std::swap (mDataTypes, land.mDataTypes); std::swap (mDataLoaded, land.mDataLoaded); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 65ac88cda3..8a8d6fdd2f 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -3,6 +3,8 @@ #include +#include + #include "esmcommon.hpp" namespace ESM @@ -31,7 +33,6 @@ struct Land // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. - ESMReader* mEsm; ESM_Context mContext; int mDataTypes; @@ -155,7 +156,9 @@ struct Land /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + bool condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable OpenThreads::Mutex mMutex; mutable int mDataLoaded; From 8aba74e6ee4df45cc442148e4e0b0f770b2159ef Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 20:23:53 +0100 Subject: [PATCH 241/765] Remove GridElement --- components/terrain/terraingrid.cpp | 20 +++++--------------- components/terrain/terraingrid.hpp | 4 +--- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4b67371e08..4d2f421e62 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -63,12 +63,6 @@ TerrainGrid::~TerrainGrid() } } -class GridElement -{ -public: - osg::ref_ptr mNode; -}; - osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { if (chunkSize * mNumSplits > 1.f) @@ -208,11 +202,9 @@ void TerrainGrid::loadCell(int x, int y) if (!terrainNode) return; // no terrain defined - std::auto_ptr element (new GridElement); - element->mNode = terrainNode; - mTerrainRoot->addChild(element->mNode); + mTerrainRoot->addChild(terrainNode); - mGrid[std::make_pair(x,y)] = element.release(); + mGrid[std::make_pair(x,y)] = terrainNode; } void TerrainGrid::unloadCell(int x, int y) @@ -221,13 +213,11 @@ void TerrainGrid::unloadCell(int x, int y) if (it == mGrid.end()) return; - GridElement* element = it->second; - mTerrainRoot->removeChild(element->mNode); + osg::Node* terrainNode = it->second; + mTerrainRoot->removeChild(terrainNode); if (mUnrefQueue.get()) - mUnrefQueue->push(element->mNode); - - delete element; + mUnrefQueue->push(terrainNode); mGrid.erase(it); } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 5708e70685..4e6a78abd4 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -14,8 +14,6 @@ namespace SceneUtil namespace Terrain { - class GridElement; - /// @brief Simple terrain implementation that loads cells in a grid, with no LOD class TerrainGrid : public Terrain::World { @@ -38,7 +36,7 @@ namespace Terrain typedef std::map > TextureCache; TextureCache mTextureCache; - typedef std::map, GridElement*> Grid; + typedef std::map, osg::ref_ptr > Grid; Grid mGrid; osg::ref_ptr mUnrefQueue; From 98848c752a0621e3820f62b53f6c8b46aee3fcc6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 20:26:58 +0100 Subject: [PATCH 242/765] Make getLayerInfo thread safe --- components/esmterrain/storage.cpp | 4 ++++ components/esmterrain/storage.hpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 267b831ec5..cead73070c 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -498,6 +500,8 @@ namespace ESMTerrain Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { + OpenThreads::ScopedLock lock(mLayerInfoMutex); + // Already have this cached? std::map::iterator found = mLayerInfoMap.find(texture); if (found != mLayerInfoMap.end()) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 5b8ca953d4..9ca39e6f39 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H +#include + #include #include @@ -105,6 +107,7 @@ namespace ESMTerrain std::string getTextureName (UniqueTextureId id); std::map mLayerInfoMap; + OpenThreads::Mutex mLayerInfoMutex; Terrain::LayerInfo getLayerInfo(const std::string& texture); }; From 0865cea2111a3e641a4e78a3935adc4d3b2a649b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 20:57:30 +0100 Subject: [PATCH 243/765] Preload terrain --- apps/openmw/mwrender/renderingmanager.cpp | 17 ++--- apps/openmw/mwrender/renderingmanager.hpp | 3 +- apps/openmw/mwworld/cellpreloader.cpp | 52 +++++++++---- apps/openmw/mwworld/cellpreloader.hpp | 8 +- apps/openmw/mwworld/scene.cpp | 4 +- components/resource/resourcemanager.cpp | 5 -- components/resource/resourcemanager.hpp | 3 - components/terrain/buffercache.cpp | 4 + components/terrain/buffercache.hpp | 4 + components/terrain/terraingrid.cpp | 92 +++++++++++++++++------ components/terrain/terraingrid.hpp | 19 ++++- components/terrain/world.cpp | 1 - components/terrain/world.hpp | 6 +- 13 files changed, 152 insertions(+), 66 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be3caaeb43..177cee4eaa 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -258,16 +258,21 @@ namespace MWRender return mResourceSystem; } - SceneUtil::WorkQueue *RenderingManager::getWorkQueue() + SceneUtil::WorkQueue* RenderingManager::getWorkQueue() { return mWorkQueue.get(); } - SceneUtil::UnrefQueue *RenderingManager::getUnrefQueue() + SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() { return mUnrefQueue.get(); } + Terrain::World* RenderingManager::getTerrain() + { + return mTerrain.get(); + } + void RenderingManager::preloadCommonAssets() { osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); @@ -279,12 +284,6 @@ namespace MWRender mWorkQueue->addWorkItem(workItem); } - void RenderingManager::clearCache() - { - if (mTerrain.get()) - mTerrain->clearCache(); - } - double RenderingManager::getReferenceTime() const { return mViewer->getFrameStamp()->getReferenceTime(); @@ -838,7 +837,7 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { if (mTerrain.get()) - mTerrain->clearCache(); + mTerrain->updateCache(); mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4c850de126..4dda6f2735 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -73,11 +73,10 @@ namespace MWRender SceneUtil::WorkQueue* getWorkQueue(); SceneUtil::UnrefQueue* getUnrefQueue(); + Terrain::World* getTerrain(); void preloadCommonAssets(); - void clearCache(); - double getReferenceTime() const; osg::Group* getLightRoot(); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 047c1e99b9..e72c36c68e 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -44,10 +45,14 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager) - : mSceneManager(sceneManager) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain) + : mIsExterior(cell->getCell()->isExterior()) + , mX(cell->getCell()->getGridX()) + , mY(cell->getCell()->getGridY()) + , mSceneManager(sceneManager) , mBulletShapeManager(bulletShapeManager) , mKeyframeManager(keyframeManager) + , mTerrain(terrain) { osg::Timer timer; ListModelsVisitor visitor (mMeshes); @@ -86,8 +91,8 @@ namespace MWWorld //std::cout << "preloading " << mesh << std::endl; - mPreloadedNodes.push_back(mSceneManager->cacheInstance(mesh)); - mPreloadedShapes.push_back(mBulletShapeManager->cacheInstance(mesh)); + mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); + mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) @@ -99,7 +104,7 @@ namespace MWWorld if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); - mPreloadedKeyframes.push_back(mKeyframeManager->get(kfname)); + mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); } } @@ -111,29 +116,44 @@ namespace MWWorld // error will be shown when visiting the cell } } - std::cout << "preloaded " << mPreloadedNodes.size() << " nodes in " << preloadTimer.time_m() << std::endl; + + if (mIsExterior) + { + try + { + mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY)); + } + catch(std::exception& e) + { + } + } + + std::cout << "preloaded " << mPreloadedObjects.size() << " objects in " << preloadTimer.time_m() << std::endl; } private: typedef std::vector MeshList; + bool mIsExterior; + int mX; + int mY; MeshList mMeshes; Resource::SceneManager* mSceneManager; Resource::BulletShapeManager* mBulletShapeManager; Resource::KeyframeManager* mKeyframeManager; + Terrain::World* mTerrain; - // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state - std::vector > mPreloadedNodes; - std::vector > mPreloadedShapes; - std::vector > mPreloadedKeyframes; + // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state + std::vector > mPreloadedObjects; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. class UpdateCacheItem : public SceneUtil::WorkItem { public: - UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime) + UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime) : mReferenceTime(referenceTime) , mResourceSystem(resourceSystem) + , mTerrain(terrain) { } @@ -141,17 +161,21 @@ namespace MWWorld { osg::Timer timer; mResourceSystem->updateCache(mReferenceTime); + + mTerrain->updateCache(); std::cout << "cleared cache in " << timer.time_m() << std::endl; } private: double mReferenceTime; Resource::ResourceSystem* mResourceSystem; + Terrain::World* mTerrain; }; - CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager) + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) + , mTerrain(terrain) , mExpiryDelay(0.0) { } @@ -177,7 +201,7 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager())); + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain)); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); @@ -201,7 +225,7 @@ namespace MWWorld } // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations - mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, timestamp)); + mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp)); } void CellPreloader::setExpiryDelay(double expiryDelay) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 55e40dcbd1..32ea194c6e 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -11,6 +11,11 @@ namespace Resource class BulletShapeManager; } +namespace Terrain +{ + class World; +} + namespace MWWorld { class CellStore; @@ -18,7 +23,7 @@ namespace MWWorld class CellPreloader { public: - CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager); + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. @@ -37,6 +42,7 @@ namespace MWWorld private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; + Terrain::World* mTerrain; osg::ref_ptr mWorkQueue; double mExpiryDelay; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b0e3844934..0be26c7b69 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -416,7 +416,6 @@ namespace MWWorld mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); - mRendering.clearCache(); std::cout << "changeCellGrid took " << timer.time_m() << std::endl; } @@ -463,7 +462,7 @@ namespace MWWorld , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) { - mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager())); + mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); mPhysics->setUnrefQueue(rendering.getUnrefQueue()); @@ -549,7 +548,6 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); - mRendering.clearCache(); std::cout << "change to interior took " << timer.time_m() << std::endl; } diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index e5f6d9f437..0a9784e6b3 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -19,11 +19,6 @@ namespace Resource mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); } - void ResourceManager::clearCache() - { - mCache->clear(); - } - void ResourceManager::setExpiryDelay(double expiryDelay) { mExpiryDelay = expiryDelay; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index d2a1d10237..34fce01451 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -22,9 +22,6 @@ namespace Resource /// Clear cache entries that have not been referenced for longer than expiryDelay. virtual void updateCache(double referenceTime); - /// Clear all cache entries regardless of having external references. - virtual void clearCache(); - /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay); diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index f9c860d47b..2a72ea59b2 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include "defs.hpp" @@ -178,6 +180,7 @@ namespace Terrain osg::ref_ptr BufferCache::getUVBuffer() { + OpenThreads::ScopedLock lock(mUvBufferMutex); if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) { return mUvBufferMap[mNumVerts]; @@ -206,6 +209,7 @@ namespace Terrain osg::ref_ptr BufferCache::getIndexBuffer(unsigned int flags) { + OpenThreads::ScopedLock lock(mIndexBufferMutex); unsigned int verts = mNumVerts; if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index ca210f2380..d1a57f8116 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -17,8 +17,10 @@ namespace Terrain /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + /// @note Thread safe. osg::ref_ptr getIndexBuffer (unsigned int flags); + /// @note Thread safe. osg::ref_ptr getUVBuffer(); // TODO: add releaseGLObjects() for our vertex/element buffer objects @@ -27,8 +29,10 @@ namespace Terrain // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. std::map > mIndexBufferMap; + OpenThreads::Mutex mIndexBufferMutex; std::map > mUvBufferMap; + OpenThreads::Mutex mUvBufferMutex; unsigned int mNumVerts; }; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4d2f421e62..5b1854abc3 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include #include @@ -50,9 +52,9 @@ namespace Terrain TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) + , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) , mUnrefQueue(unrefQueue) { - mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() @@ -63,6 +65,21 @@ TerrainGrid::~TerrainGrid() } } +osg::ref_ptr TerrainGrid::cacheCell(int x, int y) +{ + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + return found->second; + } + osg::ref_ptr node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5)); + + OpenThreads::ScopedLock lock(mGridCacheMutex); + mGridCache.insert(std::make_pair(std::make_pair(x,y), node)); + return node; +} + osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { if (chunkSize * mNumSplits > 1.f) @@ -131,19 +148,22 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu unsigned int dummyTextureCounter = 0; std::vector > layerTextures; - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; - if (!texture) + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { - texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(texture); - mTextureCache[it->mDiffuseMap] = texture; + osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mDiffuseMap] = texture; + } + layerTextures.push_back(texture); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } - layerTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); } std::vector > blendmapTextures; @@ -196,11 +216,27 @@ void TerrainGrid::loadCell(int x, int y) if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded - osg::Vec2f center(x+0.5f, y+0.5f); + // try to get it from the cache + osg::ref_ptr terrainNode; + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + Grid::const_iterator found = mGridCache.find(std::make_pair(x,y)); + if (found != mGridCache.end()) + { + terrainNode = found->second; + if (!terrainNode) + return; // no terrain defined + } + } - osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + // didn't find in cache, build it if (!terrainNode) - return; // no terrain defined + { + osg::Vec2f center(x+0.5f, y+0.5f); + terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) + return; // no terrain defined + } mTerrainRoot->addChild(terrainNode); @@ -213,7 +249,7 @@ void TerrainGrid::unloadCell(int x, int y) if (it == mGrid.end()) return; - osg::Node* terrainNode = it->second; + osg::ref_ptr terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); if (mUnrefQueue.get()) @@ -222,18 +258,28 @@ void TerrainGrid::unloadCell(int x, int y) mGrid.erase(it); } -void TerrainGrid::clearCache() +void TerrainGrid::updateCache() { - for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { - if (it->second->referenceCount() <= 1) + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) { - if (mUnrefQueue.get()) - mUnrefQueue->push(it->second); - mTextureCache.erase(it++); + if (it->second->referenceCount() <= 1) + mTextureCache.erase(it++); + else + ++it; + } + } + + { + OpenThreads::ScopedLock lock(mGridCacheMutex); + for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();) + { + if (it->second->referenceCount() <= 1) + mGridCache.erase(it++); + else + ++it; } - else - ++it; } } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 4e6a78abd4..46a95e8173 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -21,11 +21,20 @@ namespace Terrain TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); + /// Load a terrain cell and store it in cache for later use. + /// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed. + /// @note Thread safe. + virtual osg::ref_ptr cacheCell(int x, int y); + + /// @note Not thread safe. virtual void loadCell(int x, int y); + + /// @note Not thread safe. virtual void unloadCell(int x, int y); - /// Clear cached textures that are no longer referenced - void clearCache(); + /// Clear cached objects that are no longer referenced + /// @note Thread safe. + void updateCache(); private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); @@ -35,10 +44,16 @@ namespace Terrain typedef std::map > TextureCache; TextureCache mTextureCache; + OpenThreads::Mutex mTextureCacheMutex; typedef std::map, osg::ref_ptr > Grid; Grid mGrid; + Grid mGridCache; + OpenThreads::Mutex mGridCacheMutex; + + BufferCache mCache; + osg::ref_ptr mUnrefQueue; }; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2250b593d2..b56e87e1d5 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -11,7 +11,6 @@ namespace Terrain World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask) : mStorage(storage) - , mCache(storage->getCellVertices()) , mParent(parent) , mResourceSystem(resourceSystem) , mIncrementalCompileOperation(ico) diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index bd5d0a4661..992438a6a1 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,10 +39,12 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); - virtual void clearCache() {} + virtual void updateCache() {} float getHeightAt (const osg::Vec3f& worldPos); + virtual osg::ref_ptr cacheCell(int x, int y) {return NULL;} + // This is only a hint and may be ignored by the implementation. virtual void loadCell(int x, int y) {} virtual void unloadCell(int x, int y) {} @@ -52,8 +54,6 @@ namespace Terrain protected: Storage* mStorage; - BufferCache mCache; - osg::ref_ptr mParent; osg::ref_ptr mTerrainRoot; From 9f729667fb064ab5d7380a103da5180a940279a6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 21:06:02 +0100 Subject: [PATCH 244/765] Remove debug output --- apps/openmw/mwworld/cellpreloader.cpp | 11 ----------- apps/openmw/mwworld/scene.cpp | 7 ------- components/nifosg/controller.hpp | 1 - components/resource/scenemanager.cpp | 2 -- 4 files changed, 21 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index e72c36c68e..ce9a87beba 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -54,7 +54,6 @@ namespace MWWorld , mKeyframeManager(keyframeManager) , mTerrain(terrain) { - osg::Timer timer; ListModelsVisitor visitor (mMeshes); if (cell->getState() == MWWorld::CellStore::State_Loaded) { @@ -73,15 +72,11 @@ namespace MWWorld mMeshes.push_back(model); } } - std::cout << "listed models in " << timer.time_m() << std::endl; } /// Preload work to be called from the worker thread. virtual void doWork() { - // TODO: make CellStore::loadRefs thread safe so we can call it from here - - osg::Timer preloadTimer; for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) { try @@ -89,8 +84,6 @@ namespace MWWorld std::string mesh = *it; mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - //std::cout << "preloading " << mesh << std::endl; - mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); @@ -127,8 +120,6 @@ namespace MWWorld { } } - - std::cout << "preloaded " << mPreloadedObjects.size() << " objects in " << preloadTimer.time_m() << std::endl; } private: @@ -159,11 +150,9 @@ namespace MWWorld virtual void doWork() { - osg::Timer timer; mResourceSystem->updateCache(mReferenceTime); mTerrain->updateCache(); - std::cout << "cleared cache in " << timer.time_m() << std::endl; } private: diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0be26c7b69..308809bd26 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -332,7 +331,6 @@ namespace MWWorld void Scene::changeCellGrid (int X, int Y) { - osg::Timer timer; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -416,8 +414,6 @@ namespace MWWorld mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); - - std::cout << "changeCellGrid took " << timer.time_m() << std::endl; } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -488,7 +484,6 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - osg::Timer timer; CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); if(!loadcell) @@ -548,8 +543,6 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); - - std::cout << "change to interior took " << timer.time_m() << std::endl; } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 0b633a1224..ce268f5871 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 5acd5f8fb9..29f7076f0c 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -366,8 +366,6 @@ namespace Resource if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); - //std::cout << "loading " << normalized << std::endl; - mCache->addEntryToObjectCache(normalized, loaded); return loaded; } From 98c5e072f29990d8de48533ebed6f684dce7f5cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Feb 2016 21:17:10 +0100 Subject: [PATCH 245/765] Swap the terrain cache update order to make more sense --- components/terrain/terraingrid.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 5b1854abc3..f40f3a3884 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -260,17 +260,6 @@ void TerrainGrid::unloadCell(int x, int y) void TerrainGrid::updateCache() { - { - OpenThreads::ScopedLock lock(mTextureCacheMutex); - for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) - { - if (it->second->referenceCount() <= 1) - mTextureCache.erase(it++); - else - ++it; - } - } - { OpenThreads::ScopedLock lock(mGridCacheMutex); for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();) @@ -281,6 +270,17 @@ void TerrainGrid::updateCache() ++it; } } + + { + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();) + { + if (it->second->referenceCount() <= 1) + mTextureCache.erase(it++); + else + ++it; + } + } } } From 61b6806a62baa89db93f5270e5f7907ffbb73764 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Tue, 9 Feb 2016 20:23:00 -0500 Subject: [PATCH 246/765] Allow toggling of cell markers --- apps/opencs/view/render/cellmarker.cpp | 14 ++++++++++++++ apps/opencs/view/render/cellmarker.hpp | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index 17bb05440e..e8772a5867 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -7,6 +7,17 @@ #include #include +#include "mask.hpp" + +CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) +: TagBase(Mask_CellMarker), mMarker(marker) +{} + +CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const +{ + return mMarker; +} + void CSVRender::CellMarker::buildMarker() { const int characterSize = 20; @@ -50,6 +61,9 @@ CSVRender::CellMarker::CellMarker( mMarkerNode->setAutoScaleToScreen(true); mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mMarkerNode->setUserData(new CellMarkerTag(this)); + mMarkerNode->setNodeMask(Mask_CellMarker); + mCellNode->addChild(mMarkerNode); buildMarker(); diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp index 4ac9d86b00..08c7e06086 100644 --- a/apps/opencs/view/render/cellmarker.hpp +++ b/apps/opencs/view/render/cellmarker.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_CELLMARKER_H #define OPENCS_VIEW_CELLMARKER_H +#include "tagbase.hpp" + #include #include "../../model/world/cellcoordinates.hpp" @@ -13,6 +15,21 @@ namespace osg namespace CSVRender { + class CellMarker; + + class CellMarkerTag : public TagBase + { + private: + + CellMarker *mMarker; + + public: + + CellMarkerTag(CellMarker *marker); + + CellMarker *getCellMarker() const; + }; + /// \brief Marker to display cell coordinates. class CellMarker { From 5e876b1379545964c59ea4c6a7bc27b4e320968f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Feb 2016 15:34:06 +0100 Subject: [PATCH 247/765] Add missing include --- apps/openmw/mwrender/water.cpp | 1 + components/terrain/terraingrid.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a8d601f114..dba85aeb78 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index f40f3a3884..4f0e464ce2 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include From fb219fea17533812d416e9617bdda2c66b4fbf4d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 19:43:38 +0100 Subject: [PATCH 248/765] Fix respawning of NPCs/creatures when they were moved to a different cell --- apps/openmw/mwclass/creature.cpp | 10 +++++----- apps/openmw/mwclass/npc.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 6e9cfccb96..0f021b5a2e 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -772,18 +772,18 @@ namespace MWClass { if (isFlagBitSet(ptr, ESM::Creature::Respawn)) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3519f9d83e..474985f7bb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1305,18 +1305,18 @@ namespace MWClass { if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } From d3808580b032eb75232aaae02c121bb711e19e28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 16:45:56 +0100 Subject: [PATCH 249/765] Rename lightRoot to sceneRoot --- apps/openmw/mwrender/renderingmanager.cpp | 40 +++++++++++------------ apps/openmw/mwrender/renderingmanager.hpp | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 177cee4eaa..29b641f6bb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -169,26 +169,26 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - osg::ref_ptr lightRoot = new SceneUtil::LightManager; - lightRoot->setLightingMask(Mask_Lighting); - mLightRoot = lightRoot; - lightRoot->setStartLight(1); + osg::ref_ptr sceneRoot = new SceneUtil::LightManager; + sceneRoot->setLightingMask(Mask_Lighting); + mSceneRoot = sceneRoot; + sceneRoot->setStartLight(1); - mRootNode->addChild(lightRoot); + mRootNode->addChild(sceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); - mObjects.reset(new Objects(mResourceSystem, lightRoot, mUnrefQueue.get())); + mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); - mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); + mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); - mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); + mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); - mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); @@ -203,21 +203,21 @@ namespace MWRender mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); - lightRoot->addChild(source); + sceneRoot->addChild(source); - lightRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - lightRoot->setNodeMask(Mask_Scene); - lightRoot->setName("Scene Root"); + sceneRoot->setNodeMask(Mask_Scene); + sceneRoot->setName("Scene Root"); - mSky.reset(new SkyManager(lightRoot, resourceSystem->getSceneManager())); + mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); mStateUpdater = new StateUpdater; - lightRoot->addUpdateCallback(mStateUpdater); + sceneRoot->addUpdateCallback(mStateUpdater); osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; @@ -291,7 +291,7 @@ namespace MWRender osg::Group* RenderingManager::getLightRoot() { - return mLightRoot.get(); + return mSceneRoot.get(); } void RenderingManager::setNightEyeFactor(float factor) @@ -589,7 +589,7 @@ namespace MWRender image->setPixelFormat(texture->getInternalFormat()); rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mLightRoot); + rttCamera->addChild(mSceneRoot); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); @@ -772,7 +772,7 @@ namespace MWRender { mPlayerNode = new SceneUtil::PositionAttitudeTransform; mPlayerNode->setNodeMask(Mask_Player); - mLightRoot->addChild(mPlayerNode); + mSceneRoot->addChild(mPlayerNode); } mPlayerNode->setUserDataContainer(new osg::DefaultUserDataContainer); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4dda6f2735..59692f45aa 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -197,7 +197,7 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; - osg::ref_ptr mLightRoot; + osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From 6bfeb118d7ecae3bc528ff558656c42caaa8c6ec Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Feb 2016 19:08:17 +0100 Subject: [PATCH 250/765] Fix cleanup issue --- apps/openmw/mwworld/cellpreloader.cpp | 7 +++++++ apps/openmw/mwworld/cellpreloader.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index ce9a87beba..6acb41dc3c 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -169,6 +169,13 @@ namespace MWWorld { } + CellPreloader::~CellPreloader() + { + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) + it->second.mWorkItem->waitTillDone(); + mPreloadCells.clear(); + } + void CellPreloader::preload(CellStore *cell, double timestamp) { if (!mWorkQueue) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 32ea194c6e..4373953973 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -24,6 +24,7 @@ namespace MWWorld { public: CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); + ~CellPreloader(); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. From be6ea3d607724e89e917cc3d448333e9b2d688dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Feb 2016 16:22:54 +0100 Subject: [PATCH 251/765] Account for UV coordinate flip in UVController (Fixes #3203) --- components/nifosg/controller.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index d0abc9ead2..aabe07c866 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -254,9 +254,15 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) float uScale = mUScale.interpKey(value); float vScale = mVScale.interpKey(value); + osg::Matrix flipMat; + flipMat.preMultTranslate(osg::Vec3f(0,1,0)); + flipMat.preMultScale(osg::Vec3f(1,-1,1)); + osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); mat.setTrans(uTrans, vTrans, 0); + mat = flipMat * mat * flipMat; + // setting once is enough because all other texture units share the same TexMat (see setDefaults). if (!mTextureUnits.empty()) { From 48ac0bef3ed17024957e4715b3d85396be30665f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Feb 2016 16:34:02 +0100 Subject: [PATCH 252/765] Repair save games affected by bug #3080 (Fixes #3160) --- apps/openmw/mwmechanics/aisequence.cpp | 46 ++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 04ac96b11b..71733d613d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -351,60 +351,64 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) { + MWMechanics::AiPackage* package = NULL; switch (it->mType) { case ESM::AiSequence::Ai_Wander: { - MWMechanics::AiWander* wander = new AiWander( - static_cast(it->mPackage)); - mPackages.push_back(wander); + package = new AiWander(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Travel: { - MWMechanics::AiTravel* travel = new AiTravel( - static_cast(it->mPackage)); - mPackages.push_back(travel); + package = new AiTravel(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Escort: { - MWMechanics::AiEscort* escort = new AiEscort( - static_cast(it->mPackage)); - mPackages.push_back(escort); + package = new AiEscort(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Follow: { - MWMechanics::AiFollow* follow = new AiFollow( - static_cast(it->mPackage)); - mPackages.push_back(follow); + package = new AiFollow(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Activate: { - MWMechanics::AiActivate* activate = new AiActivate( - static_cast(it->mPackage)); - mPackages.push_back(activate); + package = new AiActivate(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Combat: { - MWMechanics::AiCombat* combat = new AiCombat( - static_cast(it->mPackage)); - mPackages.push_back(combat); + package = new AiCombat(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Pursue: { - MWMechanics::AiPursue* pursue = new AiPursue( - static_cast(it->mPackage)); - mPackages.push_back(pursue); + package = new AiPursue(static_cast(it->mPackage)); break; } default: break; } + + if (!package) + continue; + + // remove previous packages if required + if (package->shouldCancelPreviousAi()) + { + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if((*it)->canCancel()) + it = mPackages.erase(it); + else + ++it; + } + } + + mPackages.push_back(package); } } From 8b596dfcbe8a142a77b0af54f257343c16469cc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 14:46:45 +0100 Subject: [PATCH 253/765] Remove support for OSG 3.2 Since commit e8662bea3133ba9dbb09b86c3abb1af39425e90d, we're using OSG functionality that contains an unfixed crash bug in version 3.2. The bug is fixed in version 3.4 (OSG commit 6351e5020371b0b72b300088a5c6772f58379b84) --- CMakeLists.txt | 2 +- apps/openmw/mwrender/animation.cpp | 14 ----------- apps/openmw/mwrender/objects.cpp | 15 ----------- apps/openmw/mwrender/sky.cpp | 4 --- components/nifosg/nifloader.cpp | 32 ------------------------ components/resource/imagemanager.cpp | 7 ------ components/resource/scenemanager.cpp | 17 ------------- components/sceneutil/clone.cpp | 5 ---- components/sceneutil/controller.cpp | 8 ------ components/sceneutil/riggeometry.cpp | 2 -- components/sdlutil/sdlgraphicswindow.cpp | 8 ------ components/terrain/terraingrid.cpp | 6 ----- 12 files changed, 1 insertion(+), 119 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52970cfb59..df2ee6f49a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,7 @@ if (USE_QT) set (OSG_QT osgQt) endif() -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.4.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 398fe53225..e3cc57bc42 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -201,17 +201,10 @@ namespace class RemoveDrawableVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &geode) - { - applyImpl(geode); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { @@ -239,17 +232,10 @@ namespace class RemoveTriBipVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &node) - { - applyImpl(node); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 516d10b959..d249a7602e 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -43,26 +43,11 @@ namespace traverse(node); } - virtual void apply(osg::Geode& geode) - { - std::vector partsysVector; - for (unsigned int i=0; i(drw)) - partsysVector.push_back(partsys); - } - - for (std::vector::iterator it = partsysVector.begin(); it != partsysVector.end(); ++it) - geode.removeDrawable(*it); - } -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) mToRemove.push_back(partsys); } -#endif void remove() { diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 40283ba275..1757314fe2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1282,11 +1282,7 @@ public: if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { SceneUtil::CompositeStateSetUpdater* composite = NULL; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if ((composite = dynamic_cast(callback))) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6c6061063e..61fbc9b966 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -934,13 +934,7 @@ namespace NifOsg updater->addParticleSystem(partsys); parentNode->addChild(updater); -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(partsys); - osg::Node* toAttach = geode.get(); -#else osg::Node* toAttach = partsys.get(); -#endif if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) parentNode->addChild(toAttach); @@ -1017,11 +1011,6 @@ namespace NifOsg triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); } -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); -#endif - if (geometry->getDataVariance() == osg::Object::DYNAMIC) { // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch @@ -1029,24 +1018,14 @@ namespace NifOsg geometry->setDataVariance(osg::Object::STATIC); osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(geometry); frameswitch->addChild(geom2); -#endif parentNode->addChild(frameswitch); } else -#if OSG_VERSION_LESS_THAN(3,3,3) - parentNode->addChild(geode); -#else parentNode->addChild(geometry); -#endif } osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) @@ -1151,21 +1130,10 @@ namespace NifOsg osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(rig); - - osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| - osg::CopyOp::DEEP_COPY_DRAWABLES)); - - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(rig); frameswitch->addChild(rig2); -#endif parentNode->addChild(frameswitch); } diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 79da7d7abf..a4278eb0f8 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -62,17 +62,10 @@ namespace Resource case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); if (exts && !exts->isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#else - osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false); - if (exts && !exts->isTextureCompressionS3TCSupported() - // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#endif { std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; return false; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 29f7076f0c..52db2decc9 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -52,23 +52,6 @@ namespace && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } - void apply(osg::Geode& geode) - { - for (unsigned int i=0;i(geode.getDrawable(i))) - { - if (isWorldSpaceParticleSystem(partsys)) - { - // HACK: Ignore the InverseWorldMatrix transform the geode is attached to - if (geode.getNumParents() && geode.getParent(0)->getNumParents()) - transformInitialParticles(partsys, geode.getParent(0)->getParent(0)); - } - geode.setNodeMask(mMask); - } - } - } - #if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. void apply(osg::Drawable& drw) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 784195e7ef..26f3f5c7cd 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -81,11 +81,6 @@ namespace SceneUtil #endif osg::Drawable* cloned = osg::clone(drawable, copyop); -#if OSG_VERSION_LESS_THAN(3,3,3) - // work around OSG 3.2 not respecting the DEEP_COPY_CALLBACK flag - if (cloned->getUpdateCallback()) - cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); -#endif return cloned; } if (dynamic_cast(drawable)) diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 7762b48d07..916d4f80bd 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -65,11 +65,7 @@ namespace SceneUtil void ControllerVisitor::apply(osg::Node &node) { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if (Controller* ctrl = dynamic_cast(callback)) @@ -96,11 +92,7 @@ namespace SceneUtil { osg::Drawable* drw = geode.getDrawable(i); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = drw->getUpdateCallback(); -#else - osg::Drawable::UpdateCallback* callback = drw->getUpdateCallback(); -#endif if (Controller* ctrl = dynamic_cast(callback)) visit(geode, *ctrl); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index be8d97a4cb..da7d424311 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -289,11 +289,9 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) _boundingBox = box; _boundingBoxComputed = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3.3 and up Drawable inherits from Node, so has a bounding sphere as well. _boundingSphere = osg::BoundingSphere(_boundingBox); _boundingSphereComputed = true; -#endif for (unsigned int i=0; idirtyBound(); } diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index a0483e84df..e5bad1f009 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -112,11 +112,7 @@ void GraphicsWindowSDL2::init() mValid = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif } @@ -133,11 +129,7 @@ bool GraphicsWindowSDL2::realizeImplementation() SDL_ShowWindow(mWindow); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif mRealized = true; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4f0e464ce2..96584172a8 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -193,13 +193,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu transform->addChild(effect); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Node* toAttach = geometry.get(); -#else - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); - osg::Node* toAttach = geode.get(); -#endif effect->addChild(toAttach); From f94722b271c83ffbbe55a46eff19619f0083d87c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 14:55:00 +0100 Subject: [PATCH 254/765] OSG 3.3.4 is the first release to include the DDS crash fix --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df2ee6f49a..520279e504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,7 @@ if (USE_QT) set (OSG_QT osgQt) endif() -find_package(OpenSceneGraph 3.4.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) From 5824619a95f5ad16dae84de41ab9a635bb16f56b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 19:28:10 +0100 Subject: [PATCH 255/765] Clean up includes --- apps/openmw/mwrender/animation.cpp | 1 - apps/openmw/mwrender/objects.cpp | 1 - apps/openmw/mwrender/sky.cpp | 1 - components/nifosg/nifloader.cpp | 1 - components/resource/imagemanager.cpp | 1 - components/resource/scenemanager.cpp | 3 --- components/sceneutil/controller.cpp | 1 - components/sceneutil/riggeometry.cpp | 1 - components/sdlutil/sdlgraphicswindow.cpp | 2 -- components/terrain/terraingrid.cpp | 1 - 10 files changed, 13 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e3cc57bc42..abca3b2667 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index d249a7602e..a13ee03ac9 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 1757314fe2..f10beca6ca 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 61fbc9b966..8f40768845 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include // resource diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index a4278eb0f8..870fa370b1 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -2,7 +2,6 @@ #include #include -#include #include diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 52db2decc9..85a4afa301 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -52,7 +51,6 @@ namespace && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. void apply(osg::Drawable& drw) { @@ -67,7 +65,6 @@ namespace partsys->setNodeMask(mMask); } } -#endif void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 916d4f80bd..0978999116 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace SceneUtil { diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index da7d424311..f915ee39c3 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include "skeleton.hpp" diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index e5bad1f009..66d53b0961 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -2,8 +2,6 @@ #include -#include - namespace SDLUtil { diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 96584172a8..981a984e48 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include From d1375cd3a3ada3e8b9a84e64e1f5d1ab7b39cbaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 23:40:50 +0100 Subject: [PATCH 256/765] Crashcatcher: limit backtrace to a sensible number of stack frames When a stack overflow occurs, trying to print the whole stack would cause the process to hang indefinitely. --- apps/openmw/crashcatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index cafd0e08a1..4f0356259a 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -149,7 +149,7 @@ static void gdb_info(pid_t pid) "info registers\n" "shell echo \"\"\n" "shell echo \"* Backtrace\"\n" - "thread apply all backtrace full\n" + "thread apply all backtrace full 1000\n" "detach\n" "quit\n", pid); fclose(f); From 383524c6881056dc3f84992d5ec8d51e2f102273 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 02:56:41 +0100 Subject: [PATCH 257/765] Run physics in fixed timesteps, use the remainder to interpolate between current and previous state Based on http://gafferongames.com/game-physics/fix-your-timestep/ --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwphysics/actor.cpp | 38 ++++++-- apps/openmw/mwphysics/actor.hpp | 17 ++++ apps/openmw/mwphysics/physicssystem.cpp | 103 ++++++++++++---------- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 15 ++-- apps/openmw/mwworld/worldimp.hpp | 4 +- 8 files changed, 117 insertions(+), 66 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 946a9a5ddb..52697d6708 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -270,7 +270,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index c99754b5cd..61022da28c 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -45,8 +45,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr updateRotation(); updateScale(); - // already called by updateScale() - //updatePosition(); + updatePosition(); updateCollisionMask(); } @@ -86,19 +85,44 @@ void Actor::updatePosition() { osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); + mPosition = position; + mPreviousPosition = position; + + updateCollisionObjectPosition(); +} + +void Actor::updateCollisionObjectPosition() +{ btTransform tr = mCollisionObject->getWorldTransform(); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); - osg::Vec3f newPosition = scaledTranslation + position; - + osg::Vec3f newPosition = scaledTranslation + mPosition; tr.setOrigin(toBullet(newPosition)); mCollisionObject->setWorldTransform(tr); } -osg::Vec3f Actor::getPosition() const +osg::Vec3f Actor::getCollisionObjectPosition() const { return toOsg(mCollisionObject->getWorldTransform().getOrigin()); } +void Actor::setPosition(const osg::Vec3f &position) +{ + mPreviousPosition = mPosition; + + mPosition = position; + updateCollisionObjectPosition(); +} + +osg::Vec3f Actor::getPosition() const +{ + return mPosition; +} + +osg::Vec3f Actor::getPreviousPosition() const +{ + return mPreviousPosition; +} + void Actor::updateRotation () { btTransform tr = mCollisionObject->getWorldTransform(); @@ -106,7 +130,7 @@ void Actor::updateRotation () tr.setRotation(toBullet(mRotation)); mCollisionObject->setWorldTransform(tr); - updatePosition(); + updateCollisionObjectPosition(); } void Actor::updateScale() @@ -122,7 +146,7 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; - updatePosition(); + updateCollisionObjectPosition(); } osg::Vec3f Actor::getHalfExtents() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 5755def740..b238547e11 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -68,8 +68,15 @@ namespace MWPhysics void updateScale(); void updateRotation(); + + /** + * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used + * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. + */ void updatePosition(); + void updateCollisionObjectPosition(); + /** * Returns the half extents of the collision body (scaled according to collision scale) */ @@ -79,8 +86,17 @@ namespace MWPhysics * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. */ + osg::Vec3f getCollisionObjectPosition() const; + + /** + * Store the current position into mPreviousPosition, then move to this position. + */ + void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; + osg::Vec3f getPreviousPosition() const; + /** * Returns the half extents of the collision body (scaled according to rendering scale) * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, @@ -138,6 +154,7 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; osg::Vec3f mPosition; + osg::Vec3f mPreviousPosition; osg::Vec3f mForce; bool mOnGround; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9f1cfc6822..ddb3d0c858 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -234,13 +234,11 @@ namespace MWPhysics } } - static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, + static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, std::map& standingCollisionTracker) { const ESM::Position& refpos = ptr.getRefData().getPosition(); - osg::Vec3f position(refpos.asVec3()); - // Early-out for totally static creatures // (Not sure if gravity should still apply?) if (!ptr.getClass().isMobile(ptr)) @@ -944,8 +942,8 @@ namespace MWPhysics if (!physactor1 || !physactor2) return false; - osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level - osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); + osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level + osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); @@ -1007,11 +1005,11 @@ namespace MWPhysics return osg::Vec3f(); } - osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) - return physactor->getPosition(); + return physactor->getCollisionObjectPosition(); else return osg::Vec3f(); } @@ -1296,54 +1294,65 @@ namespace MWPhysics mMovementResults.clear(); mTimeAccum += dt; - if(mTimeAccum >= 1.0f/60.0f) + const float physicsDt = 1.f/60.0f; + int numSteps = mTimeAccum / (physicsDt); + mTimeAccum -= numSteps * physicsDt; + + if (numSteps) { // Collision events should be available on every frame mStandingCollisions.clear(); + } - const MWBase::World *world = MWBase::Environment::get().getWorld(); - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) + const MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();++iter) + { + float waterlevel = -std::numeric_limits::max(); + const MWWorld::CellStore *cell = iter->first.getCell(); + if(cell->getCell()->hasWater()) + waterlevel = cell->getWaterLevel(); + + + const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + + bool waterCollision = false; + if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() + && cell->getCell()->hasWater() + && !world->isUnderwater(iter->first.getCell(), + osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) + waterCollision = true; + + ActorMap::iterator foundActor = mActors.find(iter->first); + if (foundActor == mActors.end()) // actor was already removed from the scene + continue; + Actor* physicActor = foundActor->second; + physicActor->setCanWaterWalk(waterCollision); + + // Slow fall reduces fall speed by a factor of (effect magnitude / 200) + float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + + osg::Vec3f position = physicActor->getPosition(); + float oldHeight = position.z(); + for (int i=0; i::max(); - const MWWorld::CellStore *cell = iter->first.getCell(); - if(cell->getCell()->hasWater()) - waterlevel = cell->getWaterLevel(); - - float oldHeight = iter->first.getRefData().getPosition().pos[2]; - - const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); - - bool waterCollision = false; - if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() - && cell->getCell()->hasWater() - && !world->isUnderwater(iter->first.getCell(), - osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) - waterCollision = true; - - ActorMap::iterator foundActor = mActors.find(iter->first); - if (foundActor == mActors.end()) // actor was already removed from the scene - continue; - Actor* physicActor = foundActor->second; - physicActor->setCanWaterWalk(waterCollision); - - // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - - osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, - world->isFlying(iter->first), - waterlevel, slowFall, mCollisionWorld, mStandingCollisions); - - float heightDiff = newpos.z() - oldHeight; - - if (heightDiff < 0) - iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); - - mMovementResults.push_back(std::make_pair(iter->first, newpos)); + position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt, + world->isFlying(iter->first), + waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + physicActor->setPosition(position); } - mTimeAccum = 0.0f; + float interpolationFactor = mTimeAccum / physicsDt; + osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); + + float heightDiff = position.z() - oldHeight; + + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + + mMovementResults.push_back(std::make_pair(iter->first, interpolated)); } + mMovementQueue.clear(); return mMovementResults; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 110b59268f..62ebf4f0e9 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -129,7 +129,7 @@ namespace MWPhysics /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. - osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; + osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index b8150ce9c9..881530c8a4 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -117,7 +117,7 @@ namespace MWWorld const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) { - osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight + osg::Vec3f pos = mPhysics->getCollisionObjectPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 137dac42e8..98517f5432 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1113,7 +1113,7 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); @@ -1201,7 +1201,8 @@ namespace MWWorld if (haveToMove && newPtr.getRefData().getBaseNode()) { mRendering->moveObject(newPtr, vec); - mPhysics->updatePosition(newPtr); + if (movePhysics) + mPhysics->updatePosition(newPtr); } if (isPlayer) { @@ -1210,7 +1211,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics) { CellStore *cell = ptr.getCell(); @@ -1221,7 +1222,7 @@ namespace MWWorld cell = getExterior(cellX, cellY); } - return moveObject(ptr, cell, x, y, z); + return moveObject(ptr, cell, x, y, z, movePhysics); } MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) @@ -1373,10 +1374,10 @@ namespace MWWorld player = iter; continue; } - moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z()); + moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false); } if(player != results.end()) - moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z()); + moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); mPhysics->debugDraw(); } @@ -3206,7 +3207,7 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); - osg::Vec3f targetPos = mPhysics->getPosition(target); + osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index cf9321da52..6cc5cdc11c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -119,7 +119,7 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); @@ -355,7 +355,7 @@ namespace MWWorld virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); + virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr virtual void scaleObject (const Ptr& ptr, float scale); From 796a4a795a95ad2ffa16de61368af9257b41b9c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 03:09:28 +0100 Subject: [PATCH 258/765] Avoid the 'spiral of death' --- apps/openmw/mwphysics/physicssystem.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ddb3d0c858..8d4c2c590c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1295,7 +1295,11 @@ namespace MWPhysics mTimeAccum += dt; const float physicsDt = 1.f/60.0f; + + const int maxAllowedSteps = 20; int numSteps = mTimeAccum / (physicsDt); + numSteps = std::min(numSteps, maxAllowedSteps); + mTimeAccum -= numSteps * physicsDt; if (numSteps) From 6fc6913424f07bda08e72c6253fe6e091f423222 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 03:34:00 +0100 Subject: [PATCH 259/765] Do not set the cursor when creating it --- components/sdlutil/sdlcursormanager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index afe2406096..b372744f54 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -239,8 +239,6 @@ namespace SDLUtil //clean up SDL_FreeSurface(surf); - - _setGUICursor(name); } } From eaf3f5a82920e7b016a3a111de0de05d89cfc68b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 04:14:05 +0100 Subject: [PATCH 260/765] Remove unused arguments --- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +--- components/sdlutil/sdlcursormanager.cpp | 6 +++--- components/sdlutil/sdlcursormanager.hpp | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 03fbc92382..f5d78aee62 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2020,13 +2020,11 @@ namespace MWGui if(image.valid()) { //everything looks good, send it to the cursor manager - Uint8 size_x = imgSetPointer->getSize().width; - Uint8 size_y = imgSetPointer->getSize().height; Uint8 hotspot_x = imgSetPointer->getHotSpot().left; Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); - mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, size_x, size_y, hotspot_x, hotspot_y); + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y); } } } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index b372744f54..ad03083deb 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -211,12 +211,12 @@ namespace SDLUtil SDL_SetCursor(mCursorMap.find(name)->second); } - void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { - _createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y); + _createCursorFromResource(name, rotDegrees, image, hotspot_x, hotspot_y); } - void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { osg::ref_ptr decompressed; diff --git a/components/sdlutil/sdlcursormanager.hpp b/components/sdlutil/sdlcursormanager.hpp index 0db578039c..f338778d1a 100644 --- a/components/sdlutil/sdlcursormanager.hpp +++ b/components/sdlutil/sdlcursormanager.hpp @@ -29,10 +29,10 @@ namespace SDLUtil /// name of the cursor we changed to ("arrow", "ibeam", etc) virtual void cursorChanged(const std::string &name); - virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); private: - void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); From 25744aaaddd7a28d434e00ae2b426e12097d4e13 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 14 Feb 2016 10:28:41 -0500 Subject: [PATCH 261/765] Update cell marker appearance - Added bounding box around marker text. Box is black when cell exists otherwise it is red. - Changed format of marker text. - Changed marker text's pivot point to be at center of text. --- apps/opencs/view/render/cell.cpp | 8 +++++++- apps/opencs/view/render/cellmarker.cpp | 27 ++++++++++++++++++++------ apps/opencs/view/render/cellmarker.hpp | 7 +++++-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 0030fd9b82..e7b135891a 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -307,7 +307,13 @@ void CSVRender::Cell::setCellArrows (int mask) void CSVRender::Cell::setCellMarker() { - mCellMarker.reset(new CellMarker(mCellNode, mCoordinates)); + bool cellExists = false; + int cellIndex = mData.getCells().searchId(mId); + if (cellIndex > -1) + { + cellExists = !mData.getCells().getRecord(cellIndex).isDeleted(); + } + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index e8772a5867..e0d270f856 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -22,14 +22,27 @@ void CSVRender::CellMarker::buildMarker() { const int characterSize = 20; - // Set up marker text containing cell's coordinates. + // Set up attributes of marker text. osg::ref_ptr markerText (new osgText::Text); - markerText->setBackdropType(osgText::Text::OUTLINE); markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); markerText->setCharacterSize(characterSize); + markerText->setAlignment(osgText::Text::CENTER_CENTER); + markerText->setDrawMode(osgText::Text::TEXT | osgText::Text::FILLEDBOUNDINGBOX); + + // If cell exists then show black bounding box otherwise show red. + if (mExists) + { + markerText->setBoundingBoxColor(osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)); + } + else + { + markerText->setBoundingBoxColor(osg::Vec4f(1.0f, 0.0f, 0.0f, 1.0f)); + } + + // Add text containing cell's coordinates. std::string coordinatesText = - "#" + boost::lexical_cast(mCoordinates.getX()) + - " " + boost::lexical_cast(mCoordinates.getY()); + boost::lexical_cast(mCoordinates.getX()) + "," + + boost::lexical_cast(mCoordinates.getY()); markerText->setText(coordinatesText); // Add text to marker node. @@ -51,9 +64,11 @@ void CSVRender::CellMarker::positionMarker() CSVRender::CellMarker::CellMarker( osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists ) : mCellNode(cellNode), - mCoordinates(coordinates) + mCoordinates(coordinates), + mExists(cellExists) { // Set up node for cell marker. mMarkerNode = new osg::AutoTransform(); diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp index 08c7e06086..4246b20b8d 100644 --- a/apps/opencs/view/render/cellmarker.hpp +++ b/apps/opencs/view/render/cellmarker.hpp @@ -38,6 +38,7 @@ namespace CSVRender osg::Group* mCellNode; osg::ref_ptr mMarkerNode; CSMWorld::CellCoordinates mCoordinates; + bool mExists; // Not implemented. CellMarker(const CellMarker&); @@ -46,7 +47,7 @@ namespace CSVRender /// \brief Build marker containing cell's coordinates. void buildMarker(); - /// \brief Position marker above center of cell. + /// \brief Position marker at center of cell. void positionMarker(); public: @@ -54,9 +55,11 @@ namespace CSVRender /// \brief Constructor. /// \param cellNode Cell to create marker for. /// \param coordinates Coordinates of cell. + /// \param cellExists Whether or not cell exists. CellMarker( osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates); + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists); ~CellMarker(); }; From 83a9a164bcba46a32c3f5dfc4e408e9b2bf2c323 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 00:27:39 +0100 Subject: [PATCH 262/765] Raise the required bullet version to 2.83 2.82 appears to have a bug that causes the player to be able to phase through certain objects (bug #1587). --- apps/openmw/mwphysics/physicssystem.cpp | 16 ---------------- components/resource/bulletshape.cpp | 14 -------------- components/resource/bulletshapemanager.hpp | 6 ++++++ 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8d4c2c590c..784910d51d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -765,18 +765,11 @@ namespace MWPhysics mLeastDistSqr(std::numeric_limits::max()) { } -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col1; -#endif if (collisionObject != mMe) { btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); @@ -1026,7 +1019,6 @@ namespace MWPhysics std::vector mResult; -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) @@ -1034,14 +1026,6 @@ namespace MWPhysics const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col0; - if (collisionObject == mTestedAgainst) - collisionObject = col1; -#endif PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) mResult.push_back(holder->getPtr()); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 6855429c36..dbdbf0c6e3 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -65,21 +65,7 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) { -#if BT_BULLET_VERSION >= 283 btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); -#else - // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions - const btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); - btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); - - // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) - btOptimizedBvh* bvh = const_cast(trishape)->getOptimizedBvh(); - TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, bvh == NULL); - // Set original shape's bvh via pointer - // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape - if (bvh) - newShape->setOptimizedBvh(bvh); -#endif return newShape; } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 14b26962ba..3fe351f90c 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -6,9 +6,15 @@ #include +#include + #include "bulletshape.hpp" #include "resourcemanager.hpp" +#if BT_BULLET_VERSION < 283 +#error "OpenMW requires Bullet version 2.83 or later" +#endif + namespace Resource { class SceneManager; From 9eb96b9cb6af2643b21617e391d317c605cf7c7d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 14:34:59 +0100 Subject: [PATCH 263/765] Parse the bullet version in FindBullet.cmake --- CMakeLists.txt | 3 +++ cmake/FindBullet.cmake | 8 ++++++++ components/resource/bulletshapemanager.hpp | 6 ------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 520279e504..f5c4681149 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,9 @@ find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) +if (NOT BULLET_FOUND OR BULLET_VERSION VERSION_LESS 283) + message(FATAL_ERROR "OpenMW requires Bullet version 2.83 or later") +endif() include_directories("." SYSTEM diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 6d5c517afd..d70815dd50 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -14,11 +14,14 @@ # # Copyright (c) 2009, Philip Lowman +# Modified for OpenMW to parse BT_BULLET_VERSION. # # Redistribution AND use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. +include(PreprocessorUtils) + set(BULLET_ROOT $ENV{BULLET_ROOT}) macro(_FIND_BULLET_LIBRARY _var) @@ -75,4 +78,9 @@ if(BULLET_FOUND) #_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) + + find_file(BULLET_BTSCALAR_FILE NAMES btScalar.h PATHS "${BULLET_INCLUDE_DIR}/LinearMath") + file(READ ${BULLET_BTSCALAR_FILE} BULLET_BTSCALAR_CONTENT) + get_preprocessor_entry(BULLET_BTSCALAR_CONTENT BT_BULLET_VERSION BULLET_VERSION) + message(STATUS "Bullet version: ${BULLET_VERSION}") endif() diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 3fe351f90c..14b26962ba 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -6,15 +6,9 @@ #include -#include - #include "bulletshape.hpp" #include "resourcemanager.hpp" -#if BT_BULLET_VERSION < 283 -#error "OpenMW requires Bullet version 2.83 or later" -#endif - namespace Resource { class SceneManager; From 3c717a63603b53b301370bdf7cdef12441582748 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 14:38:58 +0100 Subject: [PATCH 264/765] Use Qt5 on travis to match the PPA's OSG build --- CI/before_install.linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 1c02bc8d99..b8d8b45e26 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -11,11 +11,11 @@ sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev -sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev +sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev qtbase5-dev if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 +sudo cmake .. -DBUILD_SHARED_LIBS=1 -DDESIRED_QT_VERSION=5 sudo make -j4 sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so From 647a5e091f4298ed390e511d7ce9fbfa64e0aa95 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:16:48 +0100 Subject: [PATCH 265/765] Add osgQt to the repository Ensures that it will be built against the correct Qt version. --- CMakeLists.txt | 7 +- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/scenewidget.cpp | 2 +- extern/osgQt/CMakeLists.txt | 21 + extern/osgQt/GraphicsWindowQt | 193 ++++ extern/osgQt/GraphicsWindowQt.cpp | 1063 +++++++++++++++++++++++ 6 files changed, 1281 insertions(+), 7 deletions(-) create mode 100644 extern/osgQt/CMakeLists.txt create mode 100644 extern/osgQt/GraphicsWindowQt create mode 100644 extern/osgQt/GraphicsWindowQt.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c4681149..719df3e348 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,11 +229,7 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -if (USE_QT) - set (OSG_QT osgQt) -endif() - -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) @@ -570,6 +566,7 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +add_subdirectory (extern/osgQt) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c9245ca9fd..6401e42222 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -197,7 +197,7 @@ target_link_libraries(openmw-cs ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} - ${OSGQT_LIBRARIES} + ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index e5b9171e09..ba31c9b8bc 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt new file mode 100644 index 0000000000..e8a456da99 --- /dev/null +++ b/extern/osgQt/CMakeLists.txt @@ -0,0 +1,21 @@ +set(OSGQT_LIBRARY "osgQt") + +# Sources + +set(OSGQT_SOURCE_FILES + GraphicsWindowQt.cpp +) + +include_directories(${FFMPEG_INCLUDE_DIRS}) +add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) + +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + target_link_libraries(${OSGQT_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTOPENGL_LIBRARY}) +else() + qt5_use_modules(${OSGQT_LIBRARY} Core OpenGL) +endif() + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(EXTERN_OSGQT_LIBRARY ${OSGQT_LIBRARY} PARENT_SCOPE) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt new file mode 100644 index 0000000000..54d069176b --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt @@ -0,0 +1,193 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGVIEWER_GRAPHICSWINDOWQT +#define OSGVIEWER_GRAPHICSWINDOWQT + +#include + +#include + +#include +#include +#include +#include +#include + +class QInputEvent; +class QGestureEvent; + +namespace osgViewer { + class ViewerBase; +} + +namespace osgQt +{ + +// forward declarations +class GraphicsWindowQt; + +/// The function sets the WindowingSystem to Qt. +void initQtWindowingSystem(); + +/** The function sets the viewer that will be used after entering + * the Qt main loop (QCoreApplication::exec()). + * + * The function also initializes internal structures required for proper + * scene rendering. + * + * The method must be called from main thread. */ +void setViewer( osgViewer::ViewerBase *viewer ); + + +class GLWidget : public QGLWidget +{ + typedef QGLWidget inherited; + +public: + + GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( QGLContext* context, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( const QGLFormat& format, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + virtual ~GLWidget(); + + inline void setGraphicsWindow( GraphicsWindowQt* gw ) { _gw = gw; } + inline GraphicsWindowQt* getGraphicsWindow() { return _gw; } + inline const GraphicsWindowQt* getGraphicsWindow() const { return _gw; } + + inline bool getForwardKeyEvents() const { return _forwardKeyEvents; } + virtual void setForwardKeyEvents( bool f ) { _forwardKeyEvents = f; } + + inline bool getTouchEventsEnabled() const { return _touchEventsEnabled; } + void setTouchEventsEnabled( bool e ); + + void setKeyboardModifiers( QInputEvent* event ); + + virtual void keyPressEvent( QKeyEvent* event ); + virtual void keyReleaseEvent( QKeyEvent* event ); + virtual void mousePressEvent( QMouseEvent* event ); + virtual void mouseReleaseEvent( QMouseEvent* event ); + virtual void mouseDoubleClickEvent( QMouseEvent* event ); + virtual void mouseMoveEvent( QMouseEvent* event ); + virtual void wheelEvent( QWheelEvent* event ); + virtual bool gestureEvent( QGestureEvent* event ); + +protected: + + int getNumDeferredEvents() + { + QMutexLocker lock(&_deferredEventQueueMutex); + return _deferredEventQueue.count(); + } + void enqueueDeferredEvent(QEvent::Type eventType, QEvent::Type removeEventType = QEvent::None) + { + QMutexLocker lock(&_deferredEventQueueMutex); + + if (removeEventType != QEvent::None) + { + if (_deferredEventQueue.removeOne(removeEventType)) + _eventCompressor.remove(eventType); + } + + if (_eventCompressor.find(eventType) == _eventCompressor.end()) + { + _deferredEventQueue.enqueue(eventType); + _eventCompressor.insert(eventType); + } + } + void processDeferredEvents(); + + friend class GraphicsWindowQt; + GraphicsWindowQt* _gw; + + QMutex _deferredEventQueueMutex; + QQueue _deferredEventQueue; + QSet _eventCompressor; + + bool _touchEventsEnabled; + + bool _forwardKeyEvents; + qreal _devicePixelRatio; + + virtual void resizeEvent( QResizeEvent* event ); + virtual void moveEvent( QMoveEvent* event ); + virtual void glDraw(); + virtual bool event( QEvent* event ); +}; + +class GraphicsWindowQt : public osgViewer::GraphicsWindow +{ +public: + GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0 ); + GraphicsWindowQt( GLWidget* widget ); + virtual ~GraphicsWindowQt(); + + inline GLWidget* getGLWidget() { return _widget; } + inline const GLWidget* getGLWidget() const { return _widget; } + + /// deprecated + inline GLWidget* getGraphWidget() { return _widget; } + /// deprecated + inline const GLWidget* getGraphWidget() const { return _widget; } + + struct WindowData : public osg::Referenced + { + WindowData( GLWidget* widget = NULL, QWidget* parent = NULL ): _widget(widget), _parent(parent) {} + GLWidget* _widget; + QWidget* _parent; + }; + + bool init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ); + + static QGLFormat traits2qglFormat( const osg::GraphicsContext::Traits* traits ); + static void qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ); + static osg::GraphicsContext::Traits* createTraits( const QGLWidget* widget ); + + virtual bool setWindowRectangleImplementation( int x, int y, int width, int height ); + virtual void getWindowRectangle( int& x, int& y, int& width, int& height ); + virtual bool setWindowDecorationImplementation( bool windowDecoration ); + virtual bool getWindowDecoration() const; + virtual void grabFocus(); + virtual void grabFocusIfPointerInWindow(); + virtual void raiseWindow(); + virtual void setWindowName( const std::string& name ); + virtual std::string getWindowName(); + virtual void useCursor( bool cursorOn ); + virtual void setCursor( MouseCursor cursor ); + inline bool getTouchEventsEnabled() const { return _widget->getTouchEventsEnabled(); } + virtual void setTouchEventsEnabled( bool e ) { _widget->setTouchEventsEnabled(e); } + + + virtual bool valid() const; + virtual bool realizeImplementation(); + virtual bool isRealizedImplementation() const; + virtual void closeImplementation(); + virtual bool makeCurrentImplementation(); + virtual bool releaseContextImplementation(); + virtual void swapBuffersImplementation(); + virtual void runOperations(); + + virtual void requestWarpPointer( float x, float y ); + +protected: + + friend class GLWidget; + GLWidget* _widget; + bool _ownsWidget; + QCursor _currentCursor; + bool _realized; +}; + +} + +#endif diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp new file mode 100644 index 0000000000..2001c8b315 --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -0,0 +1,1063 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#include "GraphicsWindowQt" + +#include +#include +#include +#include + +#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0)) +# define USE_GESTURES +# include +# include +#endif + +using namespace osgQt; + + +class QtKeyboardMap +{ + +public: + QtKeyboardMap() + { + mKeyMap[Qt::Key_Escape ] = osgGA::GUIEventAdapter::KEY_Escape; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Home ] = osgGA::GUIEventAdapter::KEY_Home; + mKeyMap[Qt::Key_Enter ] = osgGA::GUIEventAdapter::KEY_KP_Enter; + mKeyMap[Qt::Key_End ] = osgGA::GUIEventAdapter::KEY_End; + mKeyMap[Qt::Key_Return ] = osgGA::GUIEventAdapter::KEY_Return; + mKeyMap[Qt::Key_PageUp ] = osgGA::GUIEventAdapter::KEY_Page_Up; + mKeyMap[Qt::Key_PageDown ] = osgGA::GUIEventAdapter::KEY_Page_Down; + mKeyMap[Qt::Key_Left ] = osgGA::GUIEventAdapter::KEY_Left; + mKeyMap[Qt::Key_Right ] = osgGA::GUIEventAdapter::KEY_Right; + mKeyMap[Qt::Key_Up ] = osgGA::GUIEventAdapter::KEY_Up; + mKeyMap[Qt::Key_Down ] = osgGA::GUIEventAdapter::KEY_Down; + mKeyMap[Qt::Key_Backspace ] = osgGA::GUIEventAdapter::KEY_BackSpace; + mKeyMap[Qt::Key_Tab ] = osgGA::GUIEventAdapter::KEY_Tab; + mKeyMap[Qt::Key_Space ] = osgGA::GUIEventAdapter::KEY_Space; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Alt ] = osgGA::GUIEventAdapter::KEY_Alt_L; + mKeyMap[Qt::Key_Shift ] = osgGA::GUIEventAdapter::KEY_Shift_L; + mKeyMap[Qt::Key_Control ] = osgGA::GUIEventAdapter::KEY_Control_L; + mKeyMap[Qt::Key_Meta ] = osgGA::GUIEventAdapter::KEY_Meta_L; + + mKeyMap[Qt::Key_F1 ] = osgGA::GUIEventAdapter::KEY_F1; + mKeyMap[Qt::Key_F2 ] = osgGA::GUIEventAdapter::KEY_F2; + mKeyMap[Qt::Key_F3 ] = osgGA::GUIEventAdapter::KEY_F3; + mKeyMap[Qt::Key_F4 ] = osgGA::GUIEventAdapter::KEY_F4; + mKeyMap[Qt::Key_F5 ] = osgGA::GUIEventAdapter::KEY_F5; + mKeyMap[Qt::Key_F6 ] = osgGA::GUIEventAdapter::KEY_F6; + mKeyMap[Qt::Key_F7 ] = osgGA::GUIEventAdapter::KEY_F7; + mKeyMap[Qt::Key_F8 ] = osgGA::GUIEventAdapter::KEY_F8; + mKeyMap[Qt::Key_F9 ] = osgGA::GUIEventAdapter::KEY_F9; + mKeyMap[Qt::Key_F10 ] = osgGA::GUIEventAdapter::KEY_F10; + mKeyMap[Qt::Key_F11 ] = osgGA::GUIEventAdapter::KEY_F11; + mKeyMap[Qt::Key_F12 ] = osgGA::GUIEventAdapter::KEY_F12; + mKeyMap[Qt::Key_F13 ] = osgGA::GUIEventAdapter::KEY_F13; + mKeyMap[Qt::Key_F14 ] = osgGA::GUIEventAdapter::KEY_F14; + mKeyMap[Qt::Key_F15 ] = osgGA::GUIEventAdapter::KEY_F15; + mKeyMap[Qt::Key_F16 ] = osgGA::GUIEventAdapter::KEY_F16; + mKeyMap[Qt::Key_F17 ] = osgGA::GUIEventAdapter::KEY_F17; + mKeyMap[Qt::Key_F18 ] = osgGA::GUIEventAdapter::KEY_F18; + mKeyMap[Qt::Key_F19 ] = osgGA::GUIEventAdapter::KEY_F19; + mKeyMap[Qt::Key_F20 ] = osgGA::GUIEventAdapter::KEY_F20; + + mKeyMap[Qt::Key_hyphen ] = '-'; + mKeyMap[Qt::Key_Equal ] = '='; + + mKeyMap[Qt::Key_division ] = osgGA::GUIEventAdapter::KEY_KP_Divide; + mKeyMap[Qt::Key_multiply ] = osgGA::GUIEventAdapter::KEY_KP_Multiply; + mKeyMap[Qt::Key_Minus ] = '-'; + mKeyMap[Qt::Key_Plus ] = '+'; + //mKeyMap[Qt::Key_H ] = osgGA::GUIEventAdapter::KEY_KP_Home; + //mKeyMap[Qt::Key_ ] = osgGA::GUIEventAdapter::KEY_KP_Up; + //mKeyMap[92 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Up; + //mKeyMap[86 ] = osgGA::GUIEventAdapter::KEY_KP_Left; + //mKeyMap[87 ] = osgGA::GUIEventAdapter::KEY_KP_Begin; + //mKeyMap[88 ] = osgGA::GUIEventAdapter::KEY_KP_Right; + //mKeyMap[83 ] = osgGA::GUIEventAdapter::KEY_KP_End; + //mKeyMap[84 ] = osgGA::GUIEventAdapter::KEY_KP_Down; + //mKeyMap[85 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Down; + mKeyMap[Qt::Key_Insert ] = osgGA::GUIEventAdapter::KEY_KP_Insert; + //mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_KP_Delete; + } + + ~QtKeyboardMap() + { + } + + int remapKey(QKeyEvent* event) + { + KeyMap::iterator itr = mKeyMap.find(event->key()); + if (itr == mKeyMap.end()) + { + return int(*(event->text().toLatin1().data())); + } + else + return itr->second; + } + + private: + typedef std::map KeyMap; + KeyMap mKeyMap; +}; + +static QtKeyboardMap s_QtKeyboardMap; + + +/// The object responsible for the scene re-rendering. +class HeartBeat : public QObject { +public: + int _timerId; + osg::Timer _lastFrameStartTime; + osg::observer_ptr< osgViewer::ViewerBase > _viewer; + + virtual ~HeartBeat(); + + void init( osgViewer::ViewerBase *viewer ); + void stopTimer(); + void timerEvent( QTimerEvent *event ); + + static HeartBeat* instance(); +private: + HeartBeat(); + + static QPointer heartBeat; +}; + +QPointer HeartBeat::heartBeat; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) + #define GETDEVICEPIXELRATIO() 1.0 +#else + #define GETDEVICEPIXELRATIO() devicePixelRatio() +#endif + +GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, bool forwardKeyEvents ) +: QGLWidget(parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( QGLContext* context, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(context, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( const QGLFormat& format, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(format, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::~GLWidget() +{ + // close GraphicsWindowQt and remove the reference to us + if( _gw ) + { + _gw->close(); + _gw->_widget = NULL; + _gw = NULL; + } +} + +void GLWidget::setTouchEventsEnabled(bool e) +{ +#ifdef USE_GESTURES + if (e==_touchEventsEnabled) + return; + + _touchEventsEnabled = e; + + if (_touchEventsEnabled) + { + grabGesture(Qt::PinchGesture); + } + else + { + ungrabGesture(Qt::PinchGesture); + } +#endif +} + +void GLWidget::processDeferredEvents() +{ + QQueue deferredEventQueueCopy; + { + QMutexLocker lock(&_deferredEventQueueMutex); + deferredEventQueueCopy = _deferredEventQueue; + _eventCompressor.clear(); + _deferredEventQueue.clear(); + } + + while (!deferredEventQueueCopy.isEmpty()) + { + QEvent event(deferredEventQueueCopy.dequeue()); + QGLWidget::event(&event); + } +} + +bool GLWidget::event( QEvent* event ) +{ +#ifdef USE_GESTURES + if ( event->type()==QEvent::Gesture ) + return gestureEvent(static_cast(event)); +#endif + + // QEvent::Hide + // + // workaround "Qt-workaround" that does glFinish before hiding the widget + // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) + // + // Qt makes the context current, performs glFinish, and releases the context. + // This makes the problem in OSG multithreaded environment as the context + // is active in another thread, thus it can not be made current for the purpose + // of glFinish in this thread. + + // QEvent::ParentChange + // + // Reparenting GLWidget may create a new underlying window and a new GL context. + // Qt will then call doneCurrent on the GL context about to be deleted. The thread + // where old GL context was current has no longer current context to render to and + // we cannot make new GL context current in this thread. + + // We workaround above problems by deferring execution of problematic event requests. + // These events has to be enqueue and executed later in a main GUI thread (GUI operations + // outside the main thread are not allowed) just before makeCurrent is called from the + // right thread. The good place for doing that is right after swap in a swapBuffersImplementation. + + if (event->type() == QEvent::Hide) + { + // enqueue only the last of QEvent::Hide and QEvent::Show + enqueueDeferredEvent(QEvent::Hide, QEvent::Show); + return true; + } + else if (event->type() == QEvent::Show) + { + // enqueue only the last of QEvent::Show or QEvent::Hide + enqueueDeferredEvent(QEvent::Show, QEvent::Hide); + return true; + } + else if (event->type() == QEvent::ParentChange) + { + // enqueue only the last QEvent::ParentChange + enqueueDeferredEvent(QEvent::ParentChange); + return true; + } + + // perform regular event handling + return QGLWidget::event( event ); +} + +void GLWidget::setKeyboardModifiers( QInputEvent* event ) +{ + int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier); + unsigned int mask = 0; + if ( modkey & Qt::ShiftModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT; + if ( modkey & Qt::ControlModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL; + if ( modkey & Qt::AltModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_ALT; + _gw->getEventQueue()->getCurrentEventState()->setModKeyMask( mask ); +} + +void GLWidget::resizeEvent( QResizeEvent* event ) +{ + const QSize& size = event->size(); + + int scaled_width = static_cast(size.width()*_devicePixelRatio); + int scaled_height = static_cast(size.height()*_devicePixelRatio); + _gw->resized( x(), y(), scaled_width, scaled_height); + _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height ); + _gw->requestRedraw(); +} + +void GLWidget::moveEvent( QMoveEvent* event ) +{ + const QPoint& pos = event->pos(); + int scaled_width = static_cast(width()*_devicePixelRatio); + int scaled_height = static_cast(height()*_devicePixelRatio); + _gw->resized( pos.x(), pos.y(), scaled_width, scaled_height ); + _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width, scaled_height ); +} + +void GLWidget::glDraw() +{ + _gw->requestRedraw(); +} + +void GLWidget::keyPressEvent( QKeyEvent* event ) +{ + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyPress( value ); + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyPressEvent( event ); +} + +void GLWidget::keyReleaseEvent( QKeyEvent* event ) +{ + if( event->isAutoRepeat() ) + { + event->ignore(); + } + else + { + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyRelease( value ); + } + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyReleaseEvent( event ); +} + +void GLWidget::mousePressEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseReleaseEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonRelease( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseDoubleClickEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseDoubleButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseMoveEvent( QMouseEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseMotion( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio ); +} + +void GLWidget::wheelEvent( QWheelEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseScroll( + event->orientation() == Qt::Vertical ? + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN) : + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_LEFT : osgGA::GUIEventAdapter::SCROLL_RIGHT) ); +} + +#ifdef USE_GESTURES +static osgGA::GUIEventAdapter::TouchPhase translateQtGestureState( Qt::GestureState state ) +{ + osgGA::GUIEventAdapter::TouchPhase touchPhase; + switch ( state ) + { + case Qt::GestureStarted: + touchPhase = osgGA::GUIEventAdapter::TOUCH_BEGAN; + break; + case Qt::GestureUpdated: + touchPhase = osgGA::GUIEventAdapter::TOUCH_MOVED; + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + touchPhase = osgGA::GUIEventAdapter::TOUCH_ENDED; + break; + default: + touchPhase = osgGA::GUIEventAdapter::TOUCH_UNKNOWN; + }; + + return touchPhase; +} +#endif + + +bool GLWidget::gestureEvent( QGestureEvent* qevent ) +{ +#ifndef USE_GESTURES + return false; +#else + + bool accept = false; + + if ( QPinchGesture* pinch = static_cast(qevent->gesture(Qt::PinchGesture) ) ) + { + const QPointF qcenterf = pinch->centerPoint(); + const float angle = pinch->totalRotationAngle(); + const float scale = pinch->totalScaleFactor(); + + const QPoint pinchCenterQt = mapFromGlobal(qcenterf.toPoint()); + const osg::Vec2 pinchCenter( pinchCenterQt.x(), pinchCenterQt.y() ); + + //We don't have absolute positions of the two touches, only a scale and rotation + //Hence we create pseudo-coordinates which are reasonable, and centered around the + //real position + const float radius = (width()+height())/4; + const osg::Vec2 vector( scale*cos(angle)*radius, scale*sin(angle)*radius); + const osg::Vec2 p0 = pinchCenter+vector; + const osg::Vec2 p1 = pinchCenter-vector; + + osg::ref_ptr event = 0; + const osgGA::GUIEventAdapter::TouchPhase touchPhase = translateQtGestureState( pinch->state() ); + if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_BEGAN ) + { + event = _gw->getEventQueue()->touchBegan(0 , touchPhase, p0[0], p0[1] ); + } + else if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_MOVED ) + { + event = _gw->getEventQueue()->touchMoved( 0, touchPhase, p0[0], p0[1] ); + } + else + { + event = _gw->getEventQueue()->touchEnded( 0, touchPhase, p0[0], p0[1], 1 ); + } + + if ( event ) + { + event->addTouchPoint( 1, touchPhase, p1[0], p1[1] ); + accept = true; + } + } + + if ( accept ) + qevent->accept(); + + return accept; +#endif +} + + + +GraphicsWindowQt::GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +: _realized(false) +{ + + _widget = NULL; + _traits = traits; + init( parent, shareWidget, f ); +} + +GraphicsWindowQt::GraphicsWindowQt( GLWidget* widget ) +: _realized(false) +{ + _widget = widget; + _traits = _widget ? createTraits( _widget ) : new osg::GraphicsContext::Traits; + init( NULL, NULL, 0 ); +} + +GraphicsWindowQt::~GraphicsWindowQt() +{ + close(); + + // remove reference from GLWidget + if ( _widget ) + _widget->_gw = NULL; +} + +bool GraphicsWindowQt::init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +{ + // update _widget and parent by WindowData + WindowData* windowData = _traits.get() ? dynamic_cast(_traits->inheritedWindowData.get()) : 0; + if ( !_widget ) + _widget = windowData ? windowData->_widget : NULL; + if ( !parent ) + parent = windowData ? windowData->_parent : NULL; + + // create widget if it does not exist + _ownsWidget = _widget == NULL; + if ( !_widget ) + { + // shareWidget + if ( !shareWidget ) { + GraphicsWindowQt* sharedContextQt = dynamic_cast(_traits->sharedContext.get()); + if ( sharedContextQt ) + shareWidget = sharedContextQt->getGLWidget(); + } + + // WindowFlags + Qt::WindowFlags flags = f | Qt::Window | Qt::CustomizeWindowHint; + if ( _traits->windowDecoration ) + flags |= Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint +#if (QT_VERSION_CHECK(4, 5, 0) <= QT_VERSION) + | Qt::WindowCloseButtonHint +#endif + ; + + // create widget + _widget = new GLWidget( traits2qglFormat( _traits.get() ), parent, shareWidget, flags ); + } + + // set widget name and position + // (do not set it when we inherited the widget) + if ( _ownsWidget ) + { + _widget->setWindowTitle( _traits->windowName.c_str() ); + _widget->move( _traits->x, _traits->y ); + if ( !_traits->supportsResize ) _widget->setFixedSize( _traits->width, _traits->height ); + else _widget->resize( _traits->width, _traits->height ); + } + + // initialize widget properties + _widget->setAutoBufferSwap( false ); + _widget->setMouseTracking( true ); + _widget->setFocusPolicy( Qt::WheelFocus ); + _widget->setGraphicsWindow( this ); + useCursor( _traits->useCursor ); + + // initialize State + setState( new osg::State ); + getState()->setGraphicsContext(this); + + // initialize contextID + if ( _traits.valid() && _traits->sharedContext.valid() ) + { + getState()->setContextID( _traits->sharedContext->getState()->getContextID() ); + incrementContextIDUsageCount( getState()->getContextID() ); + } + else + { + getState()->setContextID( osg::GraphicsContext::createNewContextID() ); + } + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + return true; +} + +QGLFormat GraphicsWindowQt::traits2qglFormat( const osg::GraphicsContext::Traits* traits ) +{ + QGLFormat format( QGLFormat::defaultFormat() ); + + format.setAlphaBufferSize( traits->alpha ); + format.setRedBufferSize( traits->red ); + format.setGreenBufferSize( traits->green ); + format.setBlueBufferSize( traits->blue ); + format.setDepthBufferSize( traits->depth ); + format.setStencilBufferSize( traits->stencil ); + format.setSampleBuffers( traits->sampleBuffers ); + format.setSamples( traits->samples ); + + format.setAlpha( traits->alpha>0 ); + format.setDepth( traits->depth>0 ); + format.setStencil( traits->stencil>0 ); + format.setDoubleBuffer( traits->doubleBuffer ); + format.setSwapInterval( traits->vsync ? 1 : 0 ); + format.setStereo( traits->quadBufferStereo ? 1 : 0 ); + + return format; +} + +void GraphicsWindowQt::qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ) +{ + traits->red = format.redBufferSize(); + traits->green = format.greenBufferSize(); + traits->blue = format.blueBufferSize(); + traits->alpha = format.alpha() ? format.alphaBufferSize() : 0; + traits->depth = format.depth() ? format.depthBufferSize() : 0; + traits->stencil = format.stencil() ? format.stencilBufferSize() : 0; + + traits->sampleBuffers = format.sampleBuffers() ? 1 : 0; + traits->samples = format.samples(); + + traits->quadBufferStereo = format.stereo(); + traits->doubleBuffer = format.doubleBuffer(); + + traits->vsync = format.swapInterval() >= 1; +} + +osg::GraphicsContext::Traits* GraphicsWindowQt::createTraits( const QGLWidget* widget ) +{ + osg::GraphicsContext::Traits *traits = new osg::GraphicsContext::Traits; + + qglFormat2traits( widget->format(), traits ); + + QRect r = widget->geometry(); + traits->x = r.x(); + traits->y = r.y(); + traits->width = r.width(); + traits->height = r.height(); + + traits->windowName = widget->windowTitle().toLocal8Bit().data(); + Qt::WindowFlags f = widget->windowFlags(); + traits->windowDecoration = ( f & Qt::WindowTitleHint ) && + ( f & Qt::WindowMinMaxButtonsHint ) && + ( f & Qt::WindowSystemMenuHint ); + QSizePolicy sp = widget->sizePolicy(); + traits->supportsResize = sp.horizontalPolicy() != QSizePolicy::Fixed || + sp.verticalPolicy() != QSizePolicy::Fixed; + + return traits; +} + +bool GraphicsWindowQt::setWindowRectangleImplementation( int x, int y, int width, int height ) +{ + if ( _widget == NULL ) + return false; + + _widget->setGeometry( x, y, width, height ); + return true; +} + +void GraphicsWindowQt::getWindowRectangle( int& x, int& y, int& width, int& height ) +{ + if ( _widget ) + { + const QRect& geom = _widget->geometry(); + x = geom.x(); + y = geom.y(); + width = geom.width(); + height = geom.height(); + } +} + +bool GraphicsWindowQt::setWindowDecorationImplementation( bool windowDecoration ) +{ + Qt::WindowFlags flags = Qt::Window|Qt::CustomizeWindowHint;//|Qt::WindowStaysOnTopHint; + if ( windowDecoration ) + flags |= Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowSystemMenuHint; + _traits->windowDecoration = windowDecoration; + + if ( _widget ) + { + _widget->setWindowFlags( flags ); + + return true; + } + + return false; +} + +bool GraphicsWindowQt::getWindowDecoration() const +{ + return _traits->windowDecoration; +} + +void GraphicsWindowQt::grabFocus() +{ + if ( _widget ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::grabFocusIfPointerInWindow() +{ + if ( _widget->underMouse() ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::raiseWindow() +{ + if ( _widget ) + _widget->raise(); +} + +void GraphicsWindowQt::setWindowName( const std::string& name ) +{ + if ( _widget ) + _widget->setWindowTitle( name.c_str() ); +} + +std::string GraphicsWindowQt::getWindowName() +{ + return _widget ? _widget->windowTitle().toStdString() : ""; +} + +void GraphicsWindowQt::useCursor( bool cursorOn ) +{ + if ( _widget ) + { + _traits->useCursor = cursorOn; + if ( !cursorOn ) _widget->setCursor( Qt::BlankCursor ); + else _widget->setCursor( _currentCursor ); + } +} + +void GraphicsWindowQt::setCursor( MouseCursor cursor ) +{ + if ( cursor==InheritCursor && _widget ) + { + _widget->unsetCursor(); + } + + switch ( cursor ) + { + case NoCursor: _currentCursor = Qt::BlankCursor; break; + case RightArrowCursor: case LeftArrowCursor: _currentCursor = Qt::ArrowCursor; break; + case InfoCursor: _currentCursor = Qt::SizeAllCursor; break; + case DestroyCursor: _currentCursor = Qt::ForbiddenCursor; break; + case HelpCursor: _currentCursor = Qt::WhatsThisCursor; break; + case CycleCursor: _currentCursor = Qt::ForbiddenCursor; break; + case SprayCursor: _currentCursor = Qt::SizeAllCursor; break; + case WaitCursor: _currentCursor = Qt::WaitCursor; break; + case TextCursor: _currentCursor = Qt::IBeamCursor; break; + case CrosshairCursor: _currentCursor = Qt::CrossCursor; break; + case HandCursor: _currentCursor = Qt::OpenHandCursor; break; + case UpDownCursor: _currentCursor = Qt::SizeVerCursor; break; + case LeftRightCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopSideCursor: case BottomSideCursor: _currentCursor = Qt::UpArrowCursor; break; + case LeftSideCursor: case RightSideCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopLeftCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case TopRightCorner: _currentCursor = Qt::SizeFDiagCursor; break; + case BottomRightCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case BottomLeftCorner: _currentCursor = Qt::SizeFDiagCursor; break; + default: break; + }; + if ( _widget ) _widget->setCursor( _currentCursor ); +} + +bool GraphicsWindowQt::valid() const +{ + return _widget && _widget->isValid(); +} + +bool GraphicsWindowQt::realizeImplementation() +{ + // save the current context + // note: this will save only Qt-based contexts + const QGLContext *savedContext = QGLContext::currentContext(); + + // initialize GL context for the widget + if ( !valid() ) + _widget->glInit(); + + // make current + _realized = true; + bool result = makeCurrent(); + _realized = false; + + // fail if we do not have current context + if ( !result ) + { + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + OSG_WARN << "Window realize: Can make context current." << std::endl; + return false; + } + + _realized = true; + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + // make this window's context not current + // note: this must be done as we will probably make the context current from another thread + // and it is not allowed to have one context current in two threads + if( !releaseContext() ) + OSG_WARN << "Window realize: Can not release context." << std::endl; + + // restore previous context + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::isRealizedImplementation() const +{ + return _realized; +} + +void GraphicsWindowQt::closeImplementation() +{ + if ( _widget ) + _widget->close(); + _realized = false; +} + +void GraphicsWindowQt::runOperations() +{ + // While in graphics thread this is last chance to do something useful before + // graphics thread will execute its operations. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); + + GraphicsWindow::runOperations(); +} + +bool GraphicsWindowQt::makeCurrentImplementation() +{ + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + _widget->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::releaseContextImplementation() +{ + _widget->doneCurrent(); + return true; +} + +void GraphicsWindowQt::swapBuffersImplementation() +{ + _widget->swapBuffers(); + + // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but + // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will + // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the + // QGLWidget event handler is an indication of a Qt bug. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + // We need to call makeCurrent here to restore our previously current context + // which may be changed by the processDeferredEvents function. + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); +} + +void GraphicsWindowQt::requestWarpPointer( float x, float y ) +{ + if ( _widget ) + QCursor::setPos( _widget->mapToGlobal(QPoint((int)x,(int)y)) ); +} + + +class QtWindowingSystem : public osg::GraphicsContext::WindowingSystemInterface +{ +public: + + QtWindowingSystem() + { + OSG_INFO << "QtWindowingSystemInterface()" << std::endl; + } + + ~QtWindowingSystem() + { + if (osg::Referenced::getDeleteHandler()) + { + osg::Referenced::getDeleteHandler()->setNumFramesToRetainObjects(0); + osg::Referenced::getDeleteHandler()->flushAll(); + } + } + + // Access the Qt windowing system through this singleton class. + static QtWindowingSystem* getInterface() + { + static QtWindowingSystem* qtInterface = new QtWindowingSystem; + return qtInterface; + } + + // Return the number of screens present in the system + virtual unsigned int getNumScreens( const osg::GraphicsContext::ScreenIdentifier& /*si*/ ) + { + OSG_WARN << "osgQt: getNumScreens() not implemented yet." << std::endl; + return 0; + } + + // Return the resolution of specified screen + // (0,0) is returned if screen is unknown + virtual void getScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: getScreenSettings() not implemented yet." << std::endl; + } + + // Set the resolution for given screen + virtual bool setScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, const osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: setScreenSettings() not implemented yet." << std::endl; + return false; + } + + // Enumerates available resolutions + virtual void enumerateScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*screenIdentifier*/, osg::GraphicsContext::ScreenSettingsList & /*resolution*/ ) + { + OSG_WARN << "osgQt: enumerateScreenSettings() not implemented yet." << std::endl; + } + + // Create a graphics context with given traits + virtual osg::GraphicsContext* createGraphicsContext( osg::GraphicsContext::Traits* traits ) + { + if (traits->pbuffer) + { + OSG_WARN << "osgQt: createGraphicsContext - pbuffer not implemented yet." << std::endl; + return NULL; + } + else + { + osg::ref_ptr< GraphicsWindowQt > window = new GraphicsWindowQt( traits ); + if (window->valid()) return window.release(); + else return NULL; + } + } + +private: + + // No implementation for these + QtWindowingSystem( const QtWindowingSystem& ); + QtWindowingSystem& operator=( const QtWindowingSystem& ); +}; + + +// declare C entry point for static compilation. +extern "C" void graphicswindow_Qt(void) +{ + osg::GraphicsContext::setWindowingSystemInterface(QtWindowingSystem::getInterface()); +} + + +void osgQt::initQtWindowingSystem() +{ + graphicswindow_Qt(); +} + + + +void osgQt::setViewer( osgViewer::ViewerBase *viewer ) +{ + HeartBeat::instance()->init( viewer ); +} + + +/// Constructor. Must be called from main thread. +HeartBeat::HeartBeat() : _timerId( 0 ) +{ +} + + +/// Destructor. Must be called from main thread. +HeartBeat::~HeartBeat() +{ + stopTimer(); +} + +HeartBeat* HeartBeat::instance() +{ + if (!heartBeat) + { + heartBeat = new HeartBeat(); + } + return heartBeat; +} + +void HeartBeat::stopTimer() +{ + if ( _timerId != 0 ) + { + killTimer( _timerId ); + _timerId = 0; + } +} + + +/// Initializes the loop for viewer. Must be called from main thread. +void HeartBeat::init( osgViewer::ViewerBase *viewer ) +{ + if( _viewer == viewer ) + return; + + stopTimer(); + + _viewer = viewer; + + if( viewer ) + { + _timerId = startTimer( 0 ); + _lastFrameStartTime.setStartTick( 0 ); + } +} + + +void HeartBeat::timerEvent( QTimerEvent */*event*/ ) +{ + osg::ref_ptr< osgViewer::ViewerBase > viewer; + if( !_viewer.lock( viewer ) ) + { + // viewer has been deleted -> stop timer + stopTimer(); + return; + } + + // limit the frame rate + if( viewer->getRunMaxFrameRate() > 0.0) + { + double dt = _lastFrameStartTime.time_s(); + double minFrameTime = 1.0 / viewer->getRunMaxFrameRate(); + if (dt < minFrameTime) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(minFrameTime-dt))); + } + else + { + // avoid excessive CPU loading when no frame is required in ON_DEMAND mode + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + double dt = _lastFrameStartTime.time_s(); + if (dt < 0.01) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(0.01-dt))); + } + + // record start frame time + _lastFrameStartTime.setStartTick(); + + // make frame + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + if( viewer->checkNeedToDoFrame() ) + { + viewer->frame(); + } + } + else + { + viewer->frame(); + } + } +} From b77ed829b963179e4848035b55eb71ce06625040 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:17:23 +0100 Subject: [PATCH 266/765] Revert "Use Qt5 on travis to match the PPA's OSG build" This reverts commit 3c717a63603b53b301370bdf7cdef12441582748. --- CI/before_install.linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index b8d8b45e26..1c02bc8d99 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -11,11 +11,11 @@ sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev -sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev qtbase5-dev +sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 -DDESIRED_QT_VERSION=5 +sudo cmake .. -DBUILD_SHARED_LIBS=1 sudo make -j4 sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so From 80392775efe2c594005bd89ca1450dc934b9e19c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:18:06 +0100 Subject: [PATCH 267/765] Fix copy&paste error --- extern/osgQt/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt index e8a456da99..3bd08a390a 100644 --- a/extern/osgQt/CMakeLists.txt +++ b/extern/osgQt/CMakeLists.txt @@ -6,7 +6,6 @@ set(OSGQT_SOURCE_FILES GraphicsWindowQt.cpp ) -include_directories(${FFMPEG_INCLUDE_DIRS}) add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) if (DESIRED_QT_VERSION MATCHES 4) From 3f403466360af2343e0f7527f7a91c70dcc5096d Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 15 Feb 2016 19:49:54 -0500 Subject: [PATCH 268/765] Implemented a wrapper for DialInfo::SelectStruct --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/infoselectwrapper.cpp | 855 ++++++++++++++++++ apps/opencs/model/world/infoselectwrapper.hpp | 238 +++++ 3 files changed, 1094 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/world/infoselectwrapper.cpp create mode 100644 apps/opencs/model/world/infoselectwrapper.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0bde541bf7..7b825232bc 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata defaultgmsts + idcompletionmanager metadata defaultgmsts infoselectwrapper ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 0000000000..42cbabf72d --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,855 @@ +#include "infoselectwrapper.hpp" + +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Shorth Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "PC Level", + "PC Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Short || + mConstSelect.mValue.getType() == ESM::VT_Long || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // TODO these need to be checked + + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0,1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + return std::pair(IntMin, IntMax); + + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0,4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0,2); + + case Function_PcGender: + return std::pair(0,1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(IntMin, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0,0); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0,0); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + + switch (mRelationType) + { + case Relation_Equal: + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges are same, it will always be true + return rangesMatch(conditionRange, validRange); + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + case Relation_NotEqual: + // If the value is the only value withing the range, it will never be true + return rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + int functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 0000000000..8ccae0efa5 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,238 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif From ed57293e5488b6ff57c13e5cac147195ab965f5a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 14:55:13 +0100 Subject: [PATCH 269/765] Allow '^' escape characters in books http://forum.openmw.org/viewtopic.php?f=2&t=3373&p=37584&sid=1a0b015e6716b1bced37fd398ef876c7 --- components/interpreter/defines.cpp | 19 ++++++++++--------- components/interpreter/defines.hpp | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 2ceb857c40..a700253b65 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -26,13 +26,14 @@ namespace Interpreter{ return a.length() > b.length(); } - std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context) + std::string fixDefinesReal(std::string text, bool dialogue, Context& context) { unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++) { - if(text[i] == eschar) + char eschar = text[i]; + if(eschar == '%' || eschar == '^') { retval << text.substr(start, i - start); std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); @@ -113,7 +114,7 @@ namespace Interpreter{ retval << context.getCurrentCellName(); } - else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + else if(!dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } @@ -207,15 +208,15 @@ namespace Interpreter{ return retval.str (); } - std::string fixDefinesDialog(std::string text, Context& context){ - return fixDefinesReal(text, '%', false, context); + std::string fixDefinesDialog(const std::string& text, Context& context){ + return fixDefinesReal(text, true, context); } - std::string fixDefinesMsgBox(std::string text, Context& context){ - return fixDefinesReal(text, '^', false, context); + std::string fixDefinesMsgBox(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } - std::string fixDefinesBook(std::string text, Context& context){ - return fixDefinesReal(text, '%', true, context); + std::string fixDefinesBook(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } } diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp index 00c4386b88..3471b20305 100644 --- a/components/interpreter/defines.hpp +++ b/components/interpreter/defines.hpp @@ -5,9 +5,9 @@ #include "context.hpp" namespace Interpreter{ - std::string fixDefinesDialog(std::string text, Context& context); - std::string fixDefinesMsgBox(std::string text, Context& context); - std::string fixDefinesBook(std::string text, Context& context); + std::string fixDefinesDialog(const std::string& text, Context& context); + std::string fixDefinesMsgBox(const std::string& text, Context& context); + std::string fixDefinesBook(const std::string& text, Context& context); } #endif From dececf6c3878d555e94dd548c7a5f5e58c0e4efd Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 16 Feb 2016 16:02:29 +0100 Subject: [PATCH 270/765] instance moving via drag in 3D scenes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/cell.cpp | 13 ++ apps/opencs/view/render/cell.hpp | 2 + apps/opencs/view/render/instancemode.cpp | 160 +++++++++++++++++- apps/opencs/view/render/instancemode.hpp | 19 +++ apps/opencs/view/render/instancemovemode.cpp | 12 ++ apps/opencs/view/render/instancemovemode.hpp | 18 ++ apps/opencs/view/render/object.cpp | 137 ++++++++++++++- apps/opencs/view/render/object.hpp | 40 ++++- .../view/render/pagedworldspacewidget.cpp | 17 ++ .../view/render/pagedworldspacewidget.hpp | 3 + apps/opencs/view/render/scenewidget.cpp | 6 + apps/opencs/view/render/scenewidget.hpp | 3 + .../view/render/unpagedworldspacewidget.cpp | 6 + .../view/render/unpagedworldspacewidget.hpp | 3 + apps/opencs/view/render/worldspacewidget.hpp | 3 + 16 files changed, 428 insertions(+), 16 deletions(-) create mode 100644 apps/opencs/view/render/instancemovemode.cpp create mode 100644 apps/opencs/view/render/instancemovemode.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6401e42222..ce02160662 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -85,7 +85,7 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode instancemode instanceselectionmode + previewwidget editmode instancemode instanceselectionmode instancemovemode ) opencs_units_noqt (view/render diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index e7b135891a..d666304fac 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -338,3 +338,16 @@ std::vector > CSVRender::Cell::getSelection (un return result; } + +std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->isEdited()) + result.push_back (iter->second->getTag()); + + return result; +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 22f9872e3d..6cdafbf36d 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -116,6 +116,8 @@ namespace CSVRender bool isDeleted() const; std::vector > getSelection (unsigned int elementMask) const; + + std::vector > getEdited (unsigned int elementMask) const; }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4b6b2e41f8..f2fb552b00 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -18,10 +18,11 @@ #include "worldspacewidget.hpp" #include "pagedworldspacewidget.hpp" #include "instanceselectionmode.hpp" +#include "instancemovemode.hpp" CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", - parent), mSubMode (0), mSelectionMode (0) + parent), mSubMode (0), mSelectionMode (0), mDragMode (DragMode_None) { } @@ -30,12 +31,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) if (!mSubMode) { mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); - mSubMode->addButton (":placeholder", "move", - "Move selected instances" - "
    • Use primary edit to move instances around freely
    • " - "
    • Use secondary edit to move instances around within the grid
    • " - "
    " - "Not implemented yet"); + mSubMode->addButton (new InstanceMoveMode (this), "move"); mSubMode->addButton (":placeholder", "rotate", "Rotate selected instances" "