diff --git a/.gitignore b/.gitignore index 45c87a2c5..1a164592a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ files/windows/*.aps ## qt-creator CMakeLists.txt.user* .vs +.vscode ## resources data diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 514bac1ad..1f8bc6417 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ Debian: tags: - docker - linux - image: gcc + image: debian:bullseye cache: key: apt-cache paths: @@ -13,7 +13,7 @@ Debian: before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake build-essential libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libopenscenegraph-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev stage: build script: - cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi diff --git a/.travis.yml b/.travis.yml index 5ce71a7b4..d877e8acc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,10 @@ addons: apt: sources: - sourceline: 'ppa:openmw/openmw' - - ubuntu-toolchain-r-test + # - ubuntu-toolchain-r-test # for GCC-10 packages: [ # Dev - cmake, clang-tools, gcc-9, g++-9, ccache, + build-essential, cmake, clang-tools, ccache, # Boost libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg @@ -41,45 +41,26 @@ matrix: os: osx osx_image: xcode10.2 if: branch != coverity_scan - - name: OpenMW (all) on Ubuntu Bionic GCC-7 + - name: OpenMW (all) on Ubuntu Focal with GCC os: linux - dist: bionic - sudo: required + dist: focal if: branch != coverity_scan - - name: OpenMW (all) on Ubuntu Bionic GCC-9 + - name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis os: linux - dist: bionic - sudo: required - env: - - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" - if: branch != coverity_scan - - name: OpenMW (openmw) on Ubuntu Bionic Clang-6 with Static Analysis - os: linux - dist: bionic - sudo: required - env: - - MATRIX_EVAL="CC=clang && CXX=clang++" - - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - - BUILD_OPENMW_CS="OFF" - if: branch != coverity_scan - compiler: clang - - name: OpenMW (openmw-cs) on Ubuntu Bionic Clang-6 with Static Analysis - os: linux - dist: bionic - sudo: required + dist: focal env: - MATRIX_EVAL="CC=clang && CXX=clang++" - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - - BUILD_OPENMW="OFF" if: branch != coverity_scan compiler: clang - name: OpenMW Components Coverity Scan os: linux - dist: bionic - sudo: required + dist: focal if: branch = coverity_scan # allow_failures: -# - name: OpenMW (openmw) on Ubuntu Bionic Clang-6 with Static Analysis +# - name: OpenMW (openmw) on Ubuntu Focal with GCC-10 +# env: +# - MATRIX_EVAL="CC=gcc-10 && CXX=g++-10" before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi diff --git a/AUTHORS.md b/AUTHORS.md index 2c77e522e..0f0522c44 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -155,6 +155,7 @@ Programmers Paul McElroy (Greendogo) pchan3 Perry Hugh + Petr Mikheev (ptmikheev) Phillip Andrews (PhillipAnd) Pi03k Pieter van der Kloet (pvdk) diff --git a/CHANGELOG.md b/CHANGELOG.md index 434b45fb3..ce2966cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,46 @@ 0.47.0 ------ + Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1952: Incorrect particle lighting Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #3676: NiParticleColorModifier isn't applied properly + Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects + Bug #4021: Attributes and skills are not stored as floats + Bug #4623: Corprus implementation is incorrect Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5108: Savegame bloating due to inefficient fog textures format + Bug #5165: Active spells should use real time intead of timestamps Bug #5358: ForceGreeting always resets the dialogue window completely Bug #5363: Enchantment autocalc not always 0/1 Bug #5364: Script fails/stops if trying to startscript an unknown script Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key + Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5400: Editor: Verifier checks race of non-skin bodyparts + Bug #5403: Enchantment effect doesn't show on an enemy during death animation Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5416: Junk non-node records before the root node are not handled gracefully + Bug #5424: Creatures do not headtrack player + Bug #5425: Poison effect only appears for one frame + Bug #5427: GetDistance unknown ID error is misleading + Bug #5435: Enemies can't hurt the player when collision is off + Bug #5441: Enemies can't push a player character when in critical strike stance + Bug #5451: Magic projectiles don't disappear with the caster + Bug #5452: Autowalk is being included in savegames + Bug #5479: NPCs who should be walking around town are standing around without walking + Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold + Bug #5485: Intimidate doesn't increase disposition on marginal wins + Bug #5490: Hits to carried left slot aren't redistributed if there's no shield equipped + Bug #5499: Faction advance is available when requirements not met + Feature #390: 3rd person look "over the shoulder" + Feature #2386: Distant Statics in the form of Object Paging + Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog + Feature #5445: Handle NiLines + Feature #5457: Realistic diagonal movement + Task #5480: Drop Qt4 support 0.46.0 ------ @@ -188,8 +213,8 @@ Bug #5158: Objects without a name don't fallback to their ID Bug #5159: NiMaterialColorController can only control the diffuse color Bug #5161: Creature companions can't be activated when they are knocked down - Bug #5164: Faction owned items handling is incorrect Bug #5163: UserData is not copied during node cloning + Bug #5164: Faction owned items handling is incorrect Bug #5166: Scripts still should be executed after player's death Bug #5167: Player can select and cast spells before magic menu is enabled Bug #5168: Force1stPerson and Force3rdPerson commands are not really force view change @@ -231,6 +256,7 @@ Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error Bug #5352: Light source items' duration is decremented while they aren't visible Feature #1724: Handle AvoidNode + Feature #2159: "Graying out" exhausted dialogue topics Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file @@ -287,7 +313,7 @@ Feature #5147: Show spell magicka cost in spell buying window Feature #5170: Editor: Land shape editing, land selection Feature #5172: Editor: Delete instances/references with keypress in scene window - Feature #5193: Weapon sheathing + Feature #5193: Shields sheathing Feature #5201: Editor: Show tool outline in scene view, when using editmodes Feature #5219: Impelement TestCells console command Feature #5224: Handle NiKeyframeController for NiTriShape diff --git a/CI/ActivateMSVC.ps1 b/CI/ActivateMSVC.ps1 index ca78ef588..a04a23a93 100644 --- a/CI/ActivateMSVC.ps1 +++ b/CI/ActivateMSVC.ps1 @@ -1,11 +1,13 @@ & "${env:COMSPEC}" /c ActivateMSVC.bat "&&" set | ForEach-Object { - $name, $value = $_ -split '=', 2 - Set-Content env:\"$name" $value + if ($_.Contains("=")) { + $name, $value = $_ -split '=', 2 + Set-Content env:\"$name" $value + } } $MissingTools = $false -$tools = "cl", "link", "rc", "mt", "awooga" -$descriptions = "MSVC Compiler", "MSVC Linker", "MS Windows Resource Compiler", "MS Windows Manifest Tool", "A made up command" +$tools = "cl", "link", "rc", "mt" +$descriptions = "MSVC Compiler", "MSVC Linker", "MS Windows Resource Compiler", "MS Windows Manifest Tool" for ($i = 0; $i -lt $tools.Length; $i++) { $present = $true try { diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index a1c04cf66..f4aaeecc9 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -8,30 +8,16 @@ GOOGLETEST_DIR="$(pwd)/googletest/build" mkdir build cd build -if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi -if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi - ${ANALYZE} cmake \ -DCMAKE_C_COMPILER="${CC}" \ -DCMAKE_CXX_COMPILER="${CXX}" \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DBUILD_OPENMW=${BUILD_OPENMW} \ - -DBUILD_OPENCS=${BUILD_OPENMW_CS} \ - -DBUILD_LAUNCHER=${BUILD_OPENMW_CS} \ - -DBUILD_BSATOOL=${BUILD_OPENMW_CS} \ - -DBUILD_ESMTOOL=${BUILD_OPENMW_CS} \ - -DBUILD_MWINIIMPORTER=${BUILD_OPENMW_CS} \ - -DBUILD_ESSIMPORTER=${BUILD_OPENMW_CS} \ - -DBUILD_WIZARD=${BUILD_OPENMW_CS} \ - -DBUILD_NIFTEST=${BUILD_OPENMW_CS} \ - -DBUILD_UNITTESTS=1 \ - -DUSE_SYSTEM_TINYXML=1 \ - -DDESIRED_QT_VERSION=5 \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DBINDIR=/usr/games \ - -DCMAKE_BUILD_TYPE="None" \ + -DBUILD_UNITTESTS=TRUE \ -DUSE_SYSTEM_TINYXML=TRUE \ + -DCMAKE_INSTALL_PREFIX="/usr" \ + -DBINDIR="/usr/games" \ + -DCMAKE_BUILD_TYPE="DEBUG" \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ .. diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 2f40aef9c..9a213963e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -13,7 +13,16 @@ MISSINGTOOLS=0 command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; } command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; } -command -v python >/dev/null 2>&1 || { echo "Warning: Python is not on the path, automatic Qt installation impossible."; } + +MISSINGPYTHON=0 +if ! command -v python >/dev/null 2>&1; then + echo "Warning: Python is not on the path, automatic Qt installation impossible." + MISSINGPYTHON=1 +elif ! python --version >/dev/null 2>&1; then + echo "Warning: Python is (probably) fake stub Python that comes bundled with newer versions of Windows, automatic Qt installation impossible." + echo "If you think you have Python installed, try changing the order of your PATH environment variable in Advanced System Settings." + MISSINGPYTHON=1 +fi if [ $MISSINGTOOLS -ne 0 ]; then wrappedExit 1 @@ -152,7 +161,7 @@ Options: Build unit tests / Google test -u Configure for unity builds. - -v <2013/2015/2017/2019> + -v <2017/2019> Choose the Visual Studio version to use. -n Produce NMake makefiles instead of a Visual Studio solution. Cannout be used with -N. @@ -204,8 +213,8 @@ run_cmd() { shift if [ -z $VERBOSE ]; then - eval $CMD $@ > output.log 2>&1 - RET=$? + RET=0 + eval $CMD $@ > output.log 2>&1 || RET=$? if [ $RET -ne 0 ]; then if [ -z $APPVEYOR ]; then @@ -221,8 +230,9 @@ run_cmd() { return $RET else - eval $CMD $@ - return $? + RET=0 + eval $CMD $@ || RET=$? + return $RET fi } @@ -247,15 +257,16 @@ download() { printf " Downloading $FILE... " if [ -z $VERBOSE ]; then - curl --silent --retry 10 -kLy 5 -o $FILE $URL - RET=$? + RET=0 + curl --silent --retry 10 -kLy 5 -o $FILE $URL || RET=$? else - curl --retry 10 -kLy 5 -o $FILE $URL - RET=$? + RET=0 + curl --retry 10 -kLy 5 -o $FILE $URL || RET=$? fi if [ $RET -ne 0 ]; then echo "Failed!" + wrappedExit $RET else echo "Done." fi @@ -337,21 +348,13 @@ case $VS_VERSION in ;; 14|14.0|2015 ) - GENERATOR="Visual Studio 14 2015" - TOOLSET="vc140" - MSVC_REAL_VER="14" - MSVC_VER="14.0" - MSVC_YEAR="2015" - MSVC_REAL_YEAR="2015" - MSVC_DISPLAY_YEAR="2015" - BOOST_VER="1.67.0" - BOOST_VER_URL="1_67_0" - BOOST_VER_SDK="106700" + echo "Visual Studio 2015 is no longer supported" + wrappedExit 1 ;; 12|12.0|2013 ) echo "Visual Studio 2013 is no longer supported" - exit 1 + wrappedExit 1 ;; esac @@ -494,18 +497,6 @@ if [ -z $SKIP_DOWNLOAD ]; then "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" fi - # Qt - if [ -z $APPVEYOR ]; then - if [ "${MSVC_REAL_YEAR}" = "2015" ] && [ "${BITS}" = "32" ]; then - echo "Qt no longer provides MSVC2015 Win32 packages, switch to 64-bit or a newer Visual Studio. Sorry." - exit 1 - fi - - download "AQt installer" \ - "https://files.pythonhosted.org/packages/f3/bb/aee972f08deecca31bfc46b5aedfad1ce6c7f3aaf1288d685e4a914b53ac/aqtinstall-0.8-py2.py3-none-any.whl" \ - "aqtinstall-0.8-py2.py3-none-any.whl" - fi - # SDL2 download "SDL 2.0.12" \ "https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \ @@ -513,11 +504,11 @@ if [ -z $SKIP_DOWNLOAD ]; then # Google test and mock if [ ! -z $TEST_FRAMEWORK ]; then - echo "Google test 1.8.1..." + echo "Google test 1.10.0..." if [ -d googletest ]; then printf " Google test exists, skipping." else - git clone -b release-1.8.1 https://github.com/google/googletest.git + git clone -b release-1.10.0 https://github.com/google/googletest.git fi fi fi @@ -595,14 +586,8 @@ fi # Appveyor has all the boost we need already BOOST_SDK="c:/Libraries/boost_${BOOST_VER_URL}" - if [ $MSVC_REAL_VER -ge 15 ]; then - LIB_SUFFIX="1" - else - LIB_SUFFIX="0" - fi - add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}" + -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.1" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. @@ -740,28 +725,40 @@ fi fi if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - QT_SDK="$(real_pwd)/Qt/5.15.0/msvc${MSVC_REAL_YEAR}${SUFFIX}" - if [ -d 'Qt/5.15.0' ]; then + qt_version="5.15.0" + if [ "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" == "win64_msvc2017_64" ]; then + echo "This combination of options is known not to work. Falling back to Qt 5.14.2." + qt_version="5.14.2" + fi + + QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}" + + if [ -d "Qt/${qt_version}" ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then + if [ $MISSINGPYTHON -ne 0 ]; then + echo "Can't be automatically installed without Python." + wrappedExit 1 + fi + pushd "$DEPS" > /dev/null if ! [ -d 'aqt-venv' ]; then echo " Creating Virtualenv for aqt..." - eval python -m venv aqt-venv $STRIP + run_cmd python -m venv aqt-venv fi if [ -d 'aqt-venv/bin' ]; then VENV_BIN_DIR='bin' elif [ -d 'aqt-venv/Scripts' ]; then VENV_BIN_DIR='Scripts' else - echo "Error: Failed to create virtualenv." - exit 1 + echo "Error: Failed to create virtualenv in expected location." + wrappedExit 1 fi if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then echo " Installing aqt wheel into virtualenv..." - eval "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall-0.8-py2.py3-none-any.whl $STRIP + run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2 fi popd > /dev/null @@ -770,7 +767,7 @@ fi mkdir Qt cd Qt - eval "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install 5.15.0 windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" $STRIP + run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" printf " Cleaning up extraneous data... " rm -rf Qt/{aqtinstall.log,Tools} @@ -779,8 +776,7 @@ fi fi cd $QT_SDK - add_cmake_opts -DDESIRED_QT_VERSION=5 \ - -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ + add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" if [ $CONFIGURATION == "Debug" ]; then SUFFIX="d" @@ -792,8 +788,7 @@ fi echo Done. else QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}" - add_cmake_opts -DDESIRED_QT_VERSION=5 \ - -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ + add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" if [ $CONFIGURATION == "Debug" ]; then SUFFIX="d" @@ -825,7 +820,7 @@ cd $DEPS echo # Google Test and Google Mock if [ ! -z $TEST_FRAMEWORK ]; then - printf "Google test 1.8.1 ..." + printf "Google test 1.10.0 ..." cd googletest if [ ! -d build ]; then @@ -943,30 +938,18 @@ fi echo #fi -if ! [ -z $ACTIVATE_MSVC ]; then +if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } - MSVC_INSTALLATION_PATH=$(vswhere -legacy -version "[$MSVC_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath) - if [ $MSVC_REAL_VER -ge 15 ]; then - echo "@\"${MSVC_INSTALLATION_PATH}\Common7\Tools\VsDevCmd.bat\" -no_logo -arch=$([ $BITS -eq 64 ] && echo "amd64" || echo "x86") -host_arch=$([ $(uname -m) == 'x86_64' ] && echo "amd64" || echo "x86")" > ActivateMSVC.bat - else - if [ $(uname -m) == 'x86_64' ]; then - if [ $BITS -eq 64 ]; then - compiler=amd64 - else - compiler=amd64_x86 - fi - else - if [ $BITS -eq 64 ]; then - compiler=x86_amd64 - else - compiler=x86 - fi - fi - echo "@\"${MSVC_INSTALLATION_PATH}\VC\vcvarsall.bat\" $compiler" > ActivateMSVC.bat + MSVC_INSTALLATION_PATH=$(vswhere -legacy -products '*' -version "[$MSVC_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath) + if [ -z "$MSVC_INSTALLATION_PATH" ]; then + echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR" + wrappedExit 1 fi + echo "@\"${MSVC_INSTALLATION_PATH}\Common7\Tools\VsDevCmd.bat\" -no_logo -arch=$([ $BITS -eq 64 ] && echo "amd64" || echo "x86") -host_arch=$([ $(uname -m) == 'x86_64' ] && echo "amd64" || echo "x86")" > ActivateMSVC.bat + cp "../CI/activate_msvc.sh" . sed -i "s/\$MSVC_DISPLAY_YEAR/$MSVC_DISPLAY_YEAR/g" activate_msvc.sh source ./activate_msvc.sh @@ -983,39 +966,47 @@ if [ -z $VERBOSE ]; then else echo "- cmake .. $CMAKE_OPTS" fi -run_cmd cmake .. $CMAKE_OPTS -RET=$? +RET=0 +run_cmd cmake .. $CMAKE_OPTS || RET=$? if [ -z $VERBOSE ]; then if [ $RET -eq 0 ]; then echo Done. - if [ -n $ACTIVATE_MSVC ]; then - echo - echo "Note: you must manually activate MSVC for the shell in which you want to do the build." - echo - echo "Some scripts have been created in the build directory to do so in an existing shell." - echo "Bash: source activate_msvc.sh" - echo "CMD: ActivateMSVC.bat" - echo "PowerShell: ActivateMSVC.ps1" - echo - echo "You may find options to launch a Development/Native Tools/Cross Tools shell in your start menu or Visual Studio." - echo - if [ $(uname -m) == 'x86_64' ]; then - if [ $BITS -eq 64 ]; then - inheritEnvironments=msvc_x64_x64 - else - inheritEnvironments=msvc_x64 - fi - else - if [ $BITS -eq 64 ]; then - inheritEnvironments=msvc_x86_x64 - else - inheritEnvironments=msvc_x86 - fi - fi - echo "In Visual Studio 15.3 (2017 Update 3) or later, try setting '\"inheritEnvironments\": [ \"$inheritEnvironments\" ]' in CMakeSettings.json to build in the IDE." - fi else echo Failed. fi fi +if [ $RET -ne 0 ]; then + wrappedExit $RET +fi + +echo "Script completed successfully." +echo "You now have an OpenMW build system at $(unixPathAsWindows "$(pwd)")" + +if [ -n "$ACTIVATE_MSVC" ]; then + echo + echo "Note: you must manually activate MSVC for the shell in which you want to do the build." + echo + echo "Some scripts have been created in the build directory to do so in an existing shell." + echo "Bash: source activate_msvc.sh" + echo "CMD: ActivateMSVC.bat" + echo "PowerShell: ActivateMSVC.ps1" + echo + echo "You may find options to launch a Development/Native Tools/Cross Tools shell in your start menu or Visual Studio." + echo + if [ $(uname -m) == 'x86_64' ]; then + if [ $BITS -eq 64 ]; then + inheritEnvironments=msvc_x64_x64 + else + inheritEnvironments=msvc_x64 + fi + else + if [ $BITS -eq 64 ]; then + inheritEnvironments=msvc_x86_x64 + else + inheritEnvironments=msvc_x86 + fi + fi + echo "In Visual Studio 15.3 (2017 Update 3) or later, try setting '\"inheritEnvironments\": [ \"$inheritEnvironments\" ]' in CMakeSettings.json to build in the IDE." +fi + wrappedExit $RET diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 9fae97af6..01d9d2b80 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -17,7 +17,6 @@ cmake \ -D CMAKE_OSX_SYSROOT="macosx10.14" \ -D CMAKE_BUILD_TYPE=Release \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ --D DESIRED_QT_VERSION=5 \ -D BUILD_ESMTOOL=FALSE \ -G"Unix Makefiles" \ .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 14f42d00b..42005036b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,11 +20,6 @@ else() set(USE_QT TRUE) endif() -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) -endif() - # set the minimum required version across the board cmake_minimum_required(VERSION 3.1.0) @@ -154,18 +149,12 @@ endif() find_package(OpenGL REQUIRED) if (USE_QT) - 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) + find_package(Qt5Core 5.12 REQUIRED) + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Network REQUIRED) + find_package(Qt5OpenGL REQUIRED) # Instruct CMake to run moc automatically when needed. #set(CMAKE_AUTOMOC ON) - endif() endif() # Sound setup @@ -295,20 +284,12 @@ if(OSG_STATIC) list(APPEND OPENSCENEGRAPH_LIBRARIES ${OSGPlugins_LIBRARIES}) endif() -if(QT_STATIC) - if(WIN32) - if(DESIRED_QT_VERSION MATCHES 4) - # QtCore needs WSAAsyncSelect from Ws2_32.lib - set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib) - message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}") - endif() - endif() -endif() - - set(BOOST_COMPONENTS system filesystem program_options iostreams) if(WIN32) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale zlib) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) + if(MSVC) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib) + endif(MSVC) endif(WIN32) IF(BOOST_STATIC) @@ -524,10 +505,8 @@ if(WIN32) INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) - IF(DESIRED_QT_VERSION MATCHES 5) - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug) - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) - ENDIF() + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) @@ -817,7 +796,7 @@ if (WIN32) endif() # Apple bundling -if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5) +if (OPENMW_OSX_DEPLOYMENT AND APPLE) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4) message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4") endif () diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 54e946cbc..ed86a3a42 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -183,19 +183,19 @@ int list(Bsa::BSAFile& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &files = bsa.getList(); - for(unsigned int i=0; iname; - - std::string extractPath (archivePath); + for (const auto &file : bsa.getList()) + { + std::string extractPath(file.name); replaceAll(extractPath, "\\", "/"); // Get the target path (the path the file will be extracted to) @@ -278,7 +273,7 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info) // Get a stream for the file to extract // (inefficient because getFile iter on the list again) - Files::IStreamPtr data = bsa.getFile(archivePath); + Files::IStreamPtr data = bsa.getFile(file.name); bfs::ofstream out(target, std::ios::binary); // Write the file to disk diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 59095c1df..7cea574c8 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -87,7 +87,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) ("plain,p", "Print contents of dialogs, books and scripts. " "(skipped by default)" "Only affects dump mode.") - ("quiet,q", "Supress all record information. Useful for speed tests.") + ("quiet,q", "Suppress all record information. Useful for speed tests.") ("loadcells,C", "Browse through contents of all cells.") ( "encoding,e", bpo::value(&(info.encoding))-> @@ -352,12 +352,12 @@ int load(Arguments& info) std::cout << "Author: " << esm.getAuthor() << std::endl << "Description: " << esm.getDesc() << std::endl << "File format version: " << esm.getFVer() << std::endl; - std::vector m = esm.getGameFiles(); - if (!m.empty()) + std::vector masterData = esm.getGameFiles(); + if (!masterData.empty()) { std::cout << "Masters:" << std::endl; - for(unsigned int i=0;imType = type; @@ -728,10 +728,9 @@ void Record::print() << " (" << mData.mData.mAttribute[0] << ")" << std::endl; std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) << " (" << mData.mData.mAttribute[1] << ")" << std::endl; - for (int i = 0; i < 7; i++) - if (mData.mData.mSkills[i] != -1) - std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) - << " (" << mData.mData.mSkills[i] << ")" << std::endl; + for (int skill : mData.mData.mSkills) + if (skill != -1) + std::cout << " Skill: " << skillLabel(skill) << " (" << skill << ")" << std::endl; for (int i = 0; i != 10; i++) if (!mData.mRanks[i].empty()) { @@ -741,9 +740,9 @@ void Record::print() std::cout << " Attribute2 Requirement: " << mData.mData.mRankData[i].mAttribute2 << std::endl; std::cout << " One Skill at Level: " - << mData.mData.mRankData[i].mSkill1 << std::endl; + << mData.mData.mRankData[i].mPrimarySkill << std::endl; std::cout << " Two Skills at Level: " - << mData.mData.mRankData[i].mSkill2 << std::endl; + << mData.mData.mRankData[i].mFavouredSkill << std::endl; std::cout << " Faction Reaction: " << mData.mData.mRankData[i].mFactReaction << std::endl; } diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index dca38409f..bbb3dd098 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -74,7 +74,7 @@ namespace EsmTool : mIsDeleted(false) {} - std::string getId() const { + std::string getId() const override { return mData.mId; } @@ -82,15 +82,15 @@ namespace EsmTool return mData; } - void save(ESM::ESMWriter &esm) { + void save(ESM::ESMWriter &esm) override { mData.save(esm, mIsDeleted); } - void load(ESM::ESMReader &esm) { + void load(ESM::ESMReader &esm) override { mData.load(esm, mIsDeleted); } - void print(); + void print() override; }; template<> std::string Record::getId() const; diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 900fbb05c..e0756602d 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -52,9 +52,7 @@ namespace // a dynamically created record e.g. player-enchanted weapon std::string index = indexedRefId.substr(indexedRefId.size()-8); - if(index.find_first_not_of("0123456789ABCDEF") == std::string::npos ) - return true; - return false; + return index.find_first_not_of("0123456789ABCDEF") == std::string::npos; } void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId) @@ -139,12 +137,12 @@ namespace ESSImport image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); memcpy(image2->data(), &data[0], data.size()); - for (std::set >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it) + for (const auto & exploredCell : mContext->mExploredCells) { - if (it->first > mContext->mGlobalMapState.mBounds.mMaxX - || it->first < mContext->mGlobalMapState.mBounds.mMinX - || it->second > mContext->mGlobalMapState.mBounds.mMaxY - || it->second < mContext->mGlobalMapState.mBounds.mMinY) + if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX + || exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX + || exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY + || exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY) { // out of bounds, I think this could happen, since the original engine had a fixed-size map continue; @@ -152,12 +150,12 @@ namespace ESSImport int imageLeftSrc = mGlobalMapImage->s()/2; int imageTopSrc = mGlobalMapImage->t()/2; - imageLeftSrc += it->first * cellSize; - imageTopSrc -= it->second * cellSize; + imageLeftSrc += exploredCell.first * cellSize; + imageTopSrc -= exploredCell.second * cellSize; int imageLeftDst = width/2; int imageTopDst = height/2; - imageLeftDst += it->first * cellSize; - imageTopDst -= it->second * cellSize; + imageLeftDst += exploredCell.first * cellSize; + imageTopDst -= exploredCell.second * cellSize; for (int x=0; x::const_iterator refIt = cell.mRefs.begin(); refIt != cell.mRefs.end(); ++refIt) + for (const auto & cellref : cell.mRefs) { - const CellRef& cellref = *refIt; ESM::CellRef out (cellref); // TODO: use mContext->mCreatures/mNpcs @@ -437,16 +434,16 @@ namespace ESSImport void ConvertCell::write(ESM::ESMWriter &esm) { - for (std::map::const_iterator it = mIntCells.begin(); it != mIntCells.end(); ++it) - writeCell(it->second, esm); + for (const auto & cell : mIntCells) + writeCell(cell.second, esm); - for (std::map, Cell>::const_iterator it = mExtCells.begin(); it != mExtCells.end(); ++it) - writeCell(it->second, esm); + for (const auto & cell : mExtCells) + writeCell(cell.second, esm); - for (std::vector::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) + for (const auto & marker : mMarkers) { esm.startRecord(ESM::REC_MARK); - it->save(esm); + marker.save(esm); esm.endRecord(ESM::REC_MARK); } } diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 2694ea570..9a1923c2b 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -79,9 +79,9 @@ template class DefaultConverter : public Converter { public: - virtual int getStage() { return 0; } + int getStage() override { return 0; } - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { T record; bool isDeleted = false; @@ -90,7 +90,7 @@ public: mRecords[record.mId] = record; } - virtual void write(ESM::ESMWriter& esm) + void write(ESM::ESMWriter& esm) override { for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { @@ -107,7 +107,7 @@ protected: class ConvertNPC : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { ESM::NPC npc; bool isDeleted = false; @@ -127,8 +127,8 @@ public: ESM::SpellState::SpellParams 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) - mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty; + for (const auto & spell : npc.mSpells.mList) + mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty; // Clear the list now that we've written it, this prevents issues cropping up with // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. @@ -144,7 +144,7 @@ public: class ConvertCREA : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { // See comment in ConvertNPC ESM::Creature creature; @@ -162,7 +162,7 @@ public: class ConvertGlobal : public DefaultConverter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { ESM::Global global; bool isDeleted = false; @@ -183,7 +183,7 @@ public: class ConvertClass : public DefaultConverter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { ESM::Class class_; bool isDeleted = false; @@ -199,7 +199,7 @@ public: class ConvertBook : public DefaultConverter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { ESM::Book book; bool isDeleted = false; @@ -215,7 +215,7 @@ public: class ConvertNPCC : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); NPCC npcc; @@ -235,7 +235,7 @@ public: class ConvertREFR : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { REFR refr; refr.load(esm); @@ -261,7 +261,7 @@ public: } } } - virtual void write(ESM::ESMWriter& esm) + void write(ESM::ESMWriter& esm) override { esm.startRecord(ESM::REC_ASPL); esm.writeHNString("ID__", mSelectedSpell); @@ -280,14 +280,14 @@ public: mLevitationEnabled(true) {} - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { PCDT pcdt; pcdt.load(esm); convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { esm.startRecord(ESM::REC_ENAB); esm.writeHNT("TELE", mTeleportingEnabled); @@ -306,7 +306,7 @@ private: class ConvertCNTC : public Converter { - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); CNTC cntc; @@ -318,7 +318,7 @@ class ConvertCNTC : public Converter class ConvertCREC : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); CREC crec; @@ -330,8 +330,8 @@ public: class ConvertFMAP : public Converter { public: - virtual void read(ESM::ESMReader &esm); - virtual void write(ESM::ESMWriter &esm); + void read(ESM::ESMReader &esm) override; + void write(ESM::ESMWriter &esm) override; private: osg::ref_ptr mGlobalMapImage; @@ -340,8 +340,8 @@ private: class ConvertCell : public Converter { public: - virtual void read(ESM::ESMReader& esm); - virtual void write(ESM::ESMWriter& esm); + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; private: struct Cell @@ -362,7 +362,7 @@ private: class ConvertKLST : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { KLST klst; klst.load(esm); @@ -371,7 +371,7 @@ public: mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { esm.startRecord(ESM::REC_DCOU); for (std::map::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it) @@ -389,7 +389,7 @@ private: class ConvertFACT : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { ESM::Faction faction; bool isDeleted = false; @@ -409,7 +409,7 @@ public: class ConvertSTLN : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { std::string itemid = esm.getHNString("NAME"); Misc::StringUtils::lowerCaseInPlace(itemid); @@ -428,15 +428,15 @@ public: } } } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { ESM::StolenItems items; for (std::map >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) { std::map, int> owners; - for (std::set::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt) + for (const auto & ownerIt : it->second) { - owners.insert(std::make_pair(std::make_pair(ownerIt->first, ownerIt->second) + owners.insert(std::make_pair(std::make_pair(ownerIt.first, ownerIt.second) // Since OpenMW doesn't suffer from the owner contamination bug, // it needs a count argument. But for legacy savegames, we don't know // this count, so must assume all items of that ID are stolen, @@ -467,7 +467,7 @@ private: class ConvertINFO : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { INFO info; info.load(esm); @@ -477,7 +477,7 @@ public: class ConvertDIAL : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { std::string id = esm.getHNString("NAME"); DIAL dial; @@ -485,7 +485,7 @@ public: if (dial.mIndex > 0) mDials[id] = dial; } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { for (std::map::const_iterator it = mDials.begin(); it != mDials.end(); ++it) { @@ -505,7 +505,7 @@ private: class ConvertQUES : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { std::string id = esm.getHNString("NAME"); QUES quest; @@ -516,7 +516,7 @@ public: class ConvertJOUR : public Converter { public: - virtual void read(ESM::ESMReader& esm) + void read(ESM::ESMReader& esm) override { JOUR journal; journal.load(esm); @@ -531,7 +531,7 @@ public: { } - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { mGame.load(esm); mHasGame = true; @@ -551,7 +551,7 @@ public: } } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { if (!mHasGame) return; @@ -578,7 +578,7 @@ private: class ConvertSCPT : public Converter { public: - virtual void read(ESM::ESMReader &esm) + void read(ESM::ESMReader &esm) override { SCPT script; script.load(esm); @@ -586,12 +586,12 @@ public: convertSCPT(script, out); mScripts.push_back(out); } - virtual void write(ESM::ESMWriter &esm) + void write(ESM::ESMWriter &esm) override { - for (std::vector::const_iterator it = mScripts.begin(); it != mScripts.end(); ++it) + for (const auto & script : mScripts) { esm.startRecord(ESM::REC_GSCR); - it->save(esm); + script.save(esm); esm.endRecord(ESM::REC_GSCR); } } @@ -603,9 +603,9 @@ private: class ConvertPROJ : public Converter { public: - virtual int getStage() override { return 2; } - virtual void read(ESM::ESMReader& esm) override; - virtual void write(ESM::ESMWriter& esm) override; + int getStage() override { return 2; } + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; private: void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); PROJ mProj; @@ -614,8 +614,8 @@ private: class ConvertSPLM : public Converter { public: - virtual void read(ESM::ESMReader& esm) override; - virtual void write(ESM::ESMWriter& esm) override; + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; private: SPLM mSPLM; }; diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 0799c8d11..79e09488c 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -9,21 +9,20 @@ namespace ESSImport void convertInventory(const Inventory &inventory, ESM::InventoryState &state) { int index = 0; - for (std::vector::const_iterator it = inventory.mItems.begin(); - it != inventory.mItems.end(); ++it) + for (const auto & item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); - objstate.mRef = *it; - objstate.mRef.mRefID = Misc::StringUtils::lowerCase(it->mId); - objstate.mCount = std::abs(it->mCount); // restocking items have negative count in the savefile + objstate.mRef = item; + objstate.mRef.mRefID = Misc::StringUtils::lowerCase(item.mId); + objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile // openmw handles them differently, so no need to set any flags state.mItems.push_back(objstate); - if (it->mRelativeEquipmentSlot != -1) + if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about // Not a big deal, OpenMW will auto-correct to a valid slot, the only problem is when // an item could be equipped in two different slots (e.g. equipped two rings) - state.mEquipmentSlots[index] = it->mRelativeEquipmentSlot; + state.mEquipmentSlots[index] = item.mRelativeEquipmentSlot; ++index; } } diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index 5e2da2b03..b3ccbca35 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -10,13 +10,13 @@ namespace ESSImport { out.mBirthsign = pcdt.mBirthsign; out.mObject.mNpcStats.mBounty = pcdt.mBounty; - for (std::vector::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it) + for (const auto & essFaction : pcdt.mFactions) { ESM::NpcStats::Faction faction; - faction.mExpelled = (it->mFlags & 0x2) != 0; - faction.mRank = it->mRank; - faction.mReputation = it->mReputation; - out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction; + faction.mExpelled = (essFaction.mFlags & 0x2) != 0; + faction.mRank = essFaction.mRank; + faction.mReputation = essFaction.mReputation; + out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(essFaction.mFactionName.toString())] = faction; } for (int i=0; i<3; ++i) out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i]; @@ -35,10 +35,9 @@ namespace ESSImport teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled); levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled); - for (std::vector::const_iterator it = pcdt.mKnownDialogueTopics.begin(); - it != pcdt.mKnownDialogueTopics.end(); ++it) + for (const auto & knownDialogueTopic : pcdt.mKnownDialogueTopics) { - outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it)); + outDialogueTopics.push_back(Misc::StringUtils::lowerCase(knownDialogueTopic)); } controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled; diff --git a/apps/essimporter/convertscri.cpp b/apps/essimporter/convertscri.cpp index dbe35a010..eba48df77 100644 --- a/apps/essimporter/convertscri.cpp +++ b/apps/essimporter/convertscri.cpp @@ -1,18 +1,16 @@ #include "convertscri.hpp" -#include - namespace { template void storeVariables(const std::vector& variables, ESM::Locals& locals, const std::string& scriptname) { - for (typename std::vector::const_iterator it = variables.begin(); it != variables.end(); ++it) + for (const auto& variable : variables) { - ESM::Variant val(*it); + ESM::Variant val(variable); val.setType(VariantType); - locals.mVariables.push_back(std::make_pair(std::string(), val)); + locals.mVariables.emplace_back(std::string(), val); } } diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index bf48d1f78..354eca32d 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -86,7 +86,9 @@ namespace ESSImport bool mHasANIS; ANIS mANIS; // scripted animation state - void load(ESM::ESMReader& esm); + virtual void load(ESM::ESMReader& esm); + + virtual ~ActorData() = default; }; } diff --git a/apps/essimporter/importcellref.hpp b/apps/essimporter/importcellref.hpp index 556ed19bf..b115628d5 100644 --- a/apps/essimporter/importcellref.hpp +++ b/apps/essimporter/importcellref.hpp @@ -25,7 +25,9 @@ namespace ESSImport bool mDeleted; - void load(ESM::ESMReader& esm); + void load(ESM::ESMReader& esm) override; + + virtual ~CellRef() = default; }; } diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 36512579c..706512263 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -16,15 +16,12 @@ #include #include -#include #include #include #include #include #include -#include #include -#include #include @@ -49,7 +46,7 @@ namespace image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE); // need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise - std::vector::const_iterator it = fileHeader.mSCRS.begin(); + auto it = fileHeader.mSCRS.begin(); for (int y=0; y<128; ++y) { for (int x=0; x<128; ++x) @@ -317,10 +314,9 @@ namespace ESSImport std::set unknownRecords; - for (std::map >::const_iterator it = converters.begin(); - it != converters.end(); ++it) + for (const auto & converter : converters) { - it->second->setContext(context); + converter.second->setContext(context); } while (esm.hasMoreRecs()) @@ -328,7 +324,7 @@ namespace ESSImport ESM::NAME n = esm.getRecName(); esm.getRecHeader(); - std::map >::iterator it = converters.find(n.intval); + auto it = converters.find(n.intval); if (it != converters.end()) { it->second->read(esm); @@ -358,17 +354,15 @@ namespace ESSImport writer.setDescription(""); writer.setRecordCount (0); - for (std::vector::const_iterator it = header.mMaster.begin(); - it != header.mMaster.end(); ++it) - writer.addMaster (it->name, 0); // not using the size information anyway -> use value of 0 + for (const auto & master : header.mMaster) + writer.addMaster(master.name, 0); // not using the size information anyway -> use value of 0 writer.save (stream); ESM::SavedGame profile; - for (std::vector::const_iterator it = header.mMaster.begin(); - it != header.mMaster.end(); ++it) + for (const auto & master : header.mMaster) { - profile.mContentFiles.push_back(it->name); + profile.mContentFiles.push_back(master.name); } profile.mDescription = esm.getDesc(); profile.mInGameTime.mDay = context.mDay; diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 1a91b7cea..179e00f08 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -63,7 +63,6 @@ namespace ESSImport , mHour(0.f) , mNextActorId(0) { - mPlayer.mAutoMove = 0; ESM::CellId playerCellId; playerCellId.mPaged = true; playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index cff114acb..dbf9ce0bd 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -4,8 +4,6 @@ #include -#include - namespace ESSImport { diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bfc08a7d6..329d06a57 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -70,16 +70,9 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) - QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) - QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) -else() - QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) - QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) - QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) -endif() +QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) +QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) +QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) @@ -105,14 +98,7 @@ target_link_libraries(openmw-launcher components ) -if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(openmw-launcher ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY}) - if(WIN32) - target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY}) - endif(WIN32) -else() - target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) -endif() +target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index e1a5d28af..866cec728 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -104,6 +104,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. if (showOwnedIndex >= 0 && showOwnedIndex <= 3) @@ -171,6 +172,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 6bc22bad6..45c093bee 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,13 +1,9 @@ #include "graphicspage.hpp" -#include #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED @@ -55,13 +51,11 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings: bool Launcher::GraphicsPage::setupSDL() { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) bool sdlConnectSuccessful = initSDL(); if (!sdlConnectSuccessful) { return false; } -#endif int displays = SDL_GetNumVideoDisplays(); @@ -82,10 +76,8 @@ bool Launcher::GraphicsPage::setupSDL() screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); } -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) // Disconnect from SDL processes quitSDL(); -#endif return true; } @@ -145,6 +137,10 @@ bool Launcher::GraphicsPage::loadSettings() if (mEngineSettings.getBool("enable indoor shadows", "Shadows")) indoorShadowsCheckBox->setCheckState(Qt::Checked); + shadowComputeSceneBoundsComboBox->setCurrentIndex( + shadowComputeSceneBoundsComboBox->findText( + QString(tr(mEngineSettings.getString("compute scene bounds", "Shadows").c_str())))); + int shadowDistLimit = mEngineSettings.getInt("maximum shadow map distance", "Shadows"); if (shadowDistLimit > 0) { @@ -231,7 +227,7 @@ void Launcher::GraphicsPage::saveSettings() bool cPlayerShadows = playerShadowsCheckBox->checkState(); if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { - if (mEngineSettings.getBool("enable shadows", "Shadows") != true) + if (!mEngineSettings.getBool("enable shadows", "Shadows")) mEngineSettings.setBool("enable shadows", "Shadows", true); if (mEngineSettings.getBool("actor shadows", "Shadows") != cActorShadows) mEngineSettings.setBool("actor shadows", "Shadows", cActorShadows); @@ -263,6 +259,10 @@ void Launcher::GraphicsPage::saveSettings() int cShadowRes = shadowResolutionComboBox->currentText().toInt(); if (cShadowRes != mEngineSettings.getInt("shadow map resolution", "Shadows")) mEngineSettings.setInt("shadow map resolution", "Shadows", cShadowRes); + + auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); + if (cComputeSceneBounds != mEngineSettings.getString("compute scene bounds", "Shadows")) + mEngineSettings.setString("compute scene bounds", "Shadows", cComputeSceneBounds); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) @@ -316,7 +316,6 @@ QRect Launcher::GraphicsPage::getMaximumResolution() { QRect max; -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) for (QScreen* screen : QGuiApplication::screens()) { QRect res = screen->geometry(); @@ -325,17 +324,6 @@ QRect Launcher::GraphicsPage::getMaximumResolution() if (res.height() > max.height()) max.setHeight(res.height()); } -#else - int screens = QApplication::desktop()->screenCount(); - for (int i = 0; i < screens; ++i) - { - QRect res = QApplication::desktop()->screenGeometry(i); - if (res.width() > max.width()) - max.setWidth(res.width()); - if (res.height() > max.height()) - max.setHeight(res.height()); - } -#endif return max; } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index b230625fc..4ce5b584f 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -38,8 +38,8 @@ namespace Launcher Files::ConfigurationManager &mCfgMgr; Settings::Manager &mEngineSettings; - QStringList getAvailableResolutions(int screen); - QRect getMaximumResolution(); + static QStringList getAvailableResolutions(int screen); + static QRect getMaximumResolution(); bool setupSDL(); }; diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index cf720f8b8..f15abafce 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -13,18 +13,11 @@ #endif // MAC_OS_X_VERSION_MIN_REQUIRED #include "maindialog.hpp" -#include "sdlinit.hpp" int main(int argc, char *argv[]) { try { -// Note: we should init SDL2 before Qt4 to avoid crashes on Linux, -// but we should init SDL2 after Qt5 to avoid input issues on MacOS X. -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) - initSDL(); -#endif - QApplication app(argc, argv); // Internationalization @@ -50,11 +43,6 @@ int main(int argc, char *argv[]) int exitCode = app.exec(); -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) - // Disconnect from SDL processes - quitSDL(); -#endif - return exitCode; } catch (std::exception& e) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4a2e1ba0e..d88a41117 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -403,7 +403,7 @@ bool Launcher::MainDialog::setupGameData() QAbstractButton *skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); - Q_UNUSED(skipButton); // Supress compiler unused warning + Q_UNUSED(skipButton); // Suppress compiler unused warning msgBox.exec(); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 5dc2fb26f..401e7896d 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -149,16 +149,9 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) - qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) - qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) -else() - qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) - qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) - qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) -endif() +qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) +qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) +qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -236,19 +229,7 @@ target_link_libraries(openmw-cs components ) -if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(openmw-cs - ${QT_QTGUI_LIBRARY} - ${QT_QTCORE_LIBRARY} - ${QT_QTNETWORK_LIBRARY} - ${QT_QTOPENGL_LIBRARY}) - - if (WIN32) - target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY}) - endif() -else() - target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) -endif() +target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index c4b46958d..f39492c6c 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -685,7 +685,6 @@ namespace CSMPrefs std::make_pair((int)Qt::Key_ContrastAdjust , "ContrastAdjust"), std::make_pair((int)Qt::Key_LaunchG , "LaunchG"), std::make_pair((int)Qt::Key_LaunchH , "LaunchH"), -#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) std::make_pair((int)Qt::Key_TouchpadToggle , "TouchpadToggle"), std::make_pair((int)Qt::Key_TouchpadOn , "TouchpadOn"), std::make_pair((int)Qt::Key_TouchpadOff , "TouchpadOff"), @@ -706,7 +705,6 @@ namespace CSMPrefs std::make_pair((int)Qt::Key_Find , "Find"), std::make_pair((int)Qt::Key_Undo , "Undo"), std::make_pair((int)Qt::Key_Redo , "Redo"), -#endif std::make_pair((int)Qt::Key_AltGr , "AltGr"), std::make_pair((int)Qt::Key_Multi_key , "Multi_key"), std::make_pair((int)Qt::Key_Kanji , "Kanji"), @@ -770,9 +768,7 @@ namespace CSMPrefs std::make_pair((int)Qt::Key_Sleep , "Sleep"), std::make_pair((int)Qt::Key_Play , "Play"), std::make_pair((int)Qt::Key_Zoom , "Zoom"), -#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) std::make_pair((int)Qt::Key_Exit , "Exit"), -#endif std::make_pair((int)Qt::Key_Context1 , "Context1"), std::make_pair((int)Qt::Key_Context2 , "Context2"), std::make_pair((int)Qt::Key_Context3 , "Context3"), diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index a0dbd7bad..560f4215f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -116,7 +116,7 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const { if (!index.isValid()) - return 0; + return Qt::ItemFlags(); Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 7f43a9201..a8dfacb01 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -116,7 +116,7 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, Qt::ItemFlags CSMWorld::IdTree::flags (const QModelIndex & index) const { if (!index.isValid()) - return 0; + return Qt::ItemFlags(); if (index.internalId() != 0) { diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index edf325b49..e8b4102d7 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1143,8 +1143,8 @@ namespace CSMWorld case 0: return QString(faction.mRanks[subRowIndex].c_str()); case 1: return rankData.mAttribute1; case 2: return rankData.mAttribute2; - case 3: return rankData.mSkill1; - case 4: return rankData.mSkill2; + case 3: return rankData.mPrimarySkill; + case 4: return rankData.mFavouredSkill; case 5: return rankData.mFactReaction; default: throw std::runtime_error("Rank subcolumn index out of range"); } @@ -1165,8 +1165,8 @@ namespace CSMWorld case 0: faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); break; case 1: rankData.mAttribute1 = value.toInt(); break; case 2: rankData.mAttribute2 = value.toInt(); break; - case 3: rankData.mSkill1 = value.toInt(); break; - case 4: rankData.mSkill2 = value.toInt(); break; + case 3: rankData.mPrimarySkill = value.toInt(); break; + case 4: rankData.mFavouredSkill = value.toInt(); break; case 5: rankData.mFactReaction = value.toInt(); break; default: throw std::runtime_error("Rank index out of range"); } diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 8e976c68f..7b247652e 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -5,10 +5,7 @@ #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif #include "filewidget.hpp" #include "adjusterwidget.hpp" @@ -50,11 +47,7 @@ CSVDoc::NewGameDialogue::NewGameDialogue() connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), mAdjusterWidget, SLOT (setName (const QString&, bool))); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) QRect scr = QGuiApplication::primaryScreen()->geometry(); -#else - QRect scr = QApplication::desktop()->screenGeometry(); -#endif QRect rect = geometry(); move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index beb2cf7d8..3a1950a6e 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -9,10 +9,7 @@ #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif QPushButton *CSVDoc::StartupDialogue::addButton (const QString& label, const QIcon& icon) { @@ -123,12 +120,7 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) setLayout (layout); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) QRect scr = QGuiApplication::primaryScreen()->geometry(); -#else - QRect scr = QApplication::desktop()->screenGeometry(); -#endif - QRect rect = geometry(); move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index ce04f6ada..ac7c8ebf9 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -14,10 +14,7 @@ #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" @@ -1071,11 +1068,7 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) if (isGrowLimit) rect = dw->screenGeometry(this); else -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) rect = QGuiApplication::screens().at(dw->screenNumber(this))->geometry(); -#else - rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); -#endif if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) { diff --git a/apps/opencs/view/prefs/contextmenulist.hpp b/apps/opencs/view/prefs/contextmenulist.hpp index 6aa187ca4..a3b6c9735 100644 --- a/apps/opencs/view/prefs/contextmenulist.hpp +++ b/apps/opencs/view/prefs/contextmenulist.hpp @@ -14,7 +14,7 @@ namespace CSVPrefs public: - ContextMenuList(QWidget* parent = 0); + ContextMenuList(QWidget* parent = nullptr); protected: diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp index 853031ccd..7e41fcf82 100644 --- a/apps/opencs/view/prefs/dialogue.cpp +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -1,4 +1,3 @@ - #include "dialogue.hpp" #include @@ -7,10 +6,7 @@ #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif #include @@ -39,11 +35,7 @@ void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) { QString label = QString::fromUtf8 (iter->second.getKey().c_str()); -#if QT_VERSION >= QT_VERSION_CHECK(5,11,0) maxWidth = std::max (maxWidth, metrics.horizontalAdvance (label)); -#else - maxWidth = std::max (maxWidth, metrics.width (label)); -#endif list->addItem (label); } @@ -116,11 +108,7 @@ void CSVPrefs::Dialogue::show() } else { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) QRect scr = QGuiApplication::primaryScreen()->geometry(); -#else - QRect scr = QApplication::desktop()->screenGeometry(); -#endif // otherwise place at the centre of the screen QPoint screenCenter = scr.center(); diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp index 312cd1756..90c9e97f4 100644 --- a/apps/opencs/view/render/orbitcameramode.hpp +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -21,7 +21,7 @@ namespace CSVRender public: OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", - QWidget* parent = 0); + QWidget* parent = nullptr); ~OrbitCameraMode(); virtual void activate(CSVWidget::SceneToolbar* toolbar); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index e50d7b2cd..9747c3de0 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -143,14 +143,9 @@ void RenderWidget::toggleRenderStats() CompositeViewer::CompositeViewer() : mSimulationTime(0.0) { -#if QT_VERSION >= 0x050000 - // Qt5 is currently crashing and reporting "Cannot make QOpenGLContext current in a different thread" when the viewer is run multi-threaded, this is regression from Qt4 - osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::SingleThreaded; -#else - osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::DrawThreadPerContext; -#endif - - setThreadingModel(threadingModel); + // TODO: Upgrade osgQt to support osgViewer::ViewerBase::DrawThreadPerContext + // https://gitlab.com/OpenMW/openmw/-/issues/5481 + setThreadingModel(osgViewer::ViewerBase::SingleThreaded); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) setUseConfigureAffinity(false); @@ -337,7 +332,7 @@ void SceneWidget::mouseMoveEvent (QMouseEvent *event) void SceneWidget::wheelEvent(QWheelEvent *event) { - mCurrentCamControl->handleMouseScrollEvent(event->delta()); + mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y()); } void SceneWidget::update(double dt) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index c2c354274..85b898d04 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,7 +49,7 @@ namespace CSVRender Q_OBJECT public: - RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + RenderWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~RenderWidget(); /// Initiates a request to redraw the view @@ -78,8 +78,8 @@ namespace CSVRender { Q_OBJECT public: - SceneWidget(std::shared_ptr resourceSystem, QWidget* parent = 0, - Qt::WindowFlags f = 0, bool retrieveInput = true); + SceneWidget(std::shared_ptr resourceSystem, QWidget* parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags(), bool retrieveInput = true); virtual ~SceneWidget(); CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 8e54b9a81..916c349d2 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -33,7 +33,7 @@ #include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) - : SceneWidget (document.getData().getResourceSystem(), parent, 0, false) + : SceneWidget (document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false) , mSceneElements(0) , mRun(0) , mDocument(document) @@ -646,13 +646,8 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) if (mDragging) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) mDragX = event->localPos().x(); mDragY = height() - event->localPos().y(); -#else - mDragX = event->posF().x(); - mDragY = height() - event->posF().y(); -#endif } } else @@ -682,8 +677,7 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - editMode.dragWheel (event->delta(), factor); + editMode.dragWheel (event->angleDelta().y(), factor); } else SceneWidget::wheelEvent(event); diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index a970af168..426c12f68 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -144,11 +144,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), mRefreshAction (0), mRefreshState (refreshState) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); -#else - horizontalHeader()->setResizeMode (QHeaderView::Interactive); -#endif horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setSortingEnabled (true); @@ -277,7 +273,7 @@ void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) if (key.startsWith (base)) { QString modifierString = key.mid (base.size()); - Qt::KeyboardModifiers modifiers = 0; + Qt::KeyboardModifiers modifiers; if (modifierString=="-s") modifiers = Qt::ShiftModifier; diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp index cd19e54a3..1cc649313 100644 --- a/apps/opencs/view/widget/coloreditor.cpp +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -5,10 +5,7 @@ #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include -#endif #include "colorpickerpopup.hpp" @@ -99,11 +96,7 @@ QPoint CSVWidget::ColorEditor::calculatePopupPosition() { QRect editorGeometry = geometry(); QRect popupGeometry = mColorPicker->geometry(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); -#else - QRect screenGeometry = QApplication::desktop()->screenGeometry(); -#endif // Center the popup horizontally relative to the editor int localPopupX = (editorGeometry.width() - popupGeometry.width()) / 2; diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 1e2d44e7a..b53282036 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -64,13 +64,8 @@ CSVWidget::SceneToolRun::SceneToolRun (SceneToolbar *parent, const QString& tool mTable->setShowGrid (false); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); -#else - mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); -#endif mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index e4647d600..4b2d20004 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -180,13 +180,8 @@ CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const mTable->setShowGrid (true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); -#else - mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch); -#endif mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 35937f1a6..272a5de42 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -243,13 +243,8 @@ CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush (SceneToolbar *parent, c mTable->setShowGrid (true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); -#else - mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch); -#endif mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 0f920db94..8027cec62 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -110,11 +110,7 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte int valueIndex = getValueIndex(index); if (valueIndex != -1) { -#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) QStyleOptionViewItem itemOption(option); -#else - QStyleOptionViewItemV4 itemOption(option); -#endif itemOption.text = mValues.at(valueIndex).second; QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter); } @@ -134,13 +130,7 @@ QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const itemOption.state = option.state; const QString &valueText = mValues.at(valueIndex).second; - -#if QT_VERSION >= QT_VERSION_CHECK(5,11,0) QSize valueSize = QSize(itemOption.fontMetrics.horizontalAdvance(valueText), itemOption.fontMetrics.height()); -#else - QSize valueSize = QSize(itemOption.fontMetrics.width(valueText), itemOption.fontMetrics.height()); -#endif - itemOption.currentText = valueText; return QApplication::style()->sizeFromContents(QStyle::CT_ComboBox, &itemOption, valueSize); } diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 1b72211e8..d52f7ca73 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -33,11 +33,7 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); -#else - horizontalHeader()->setResizeMode (QHeaderView::Interactive); -#endif verticalHeader()->hide(); int columns = model->columnCount(QModelIndex()); diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 996e80da4..9083359d2 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -205,12 +205,7 @@ bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const void CSVWorld::ScriptEdit::setTabWidth() { // Set tab width to specified number of characters using current font. -#if QT_VERSION >= QT_VERSION_CHECK(5,11,0) setTabStopDistance(mTabCharCount * fontMetrics().horizontalAdvance(' ')); -#else - setTabStopWidth(mTabCharCount * fontMetrics().width(' ')); -#endif - } void CSVWorld::ScriptEdit::wrapLines(bool wrap) @@ -290,12 +285,7 @@ int CSVWorld::ScriptEdit::lineNumberAreaWidth() ++digits; } -#if QT_VERSION >= QT_VERSION_CHECK(5,11,0) int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; -#else - int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; -#endif - return space; } diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index ca7cbd159..45809b28c 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -83,13 +83,8 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QStringList headers; headers << "Severity" << "Line" << "Description"; setHorizontalHeaderLabels (headers); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); -#else - horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); - horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); -#endif horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setColumnHidden (3, true); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index e2bc87a72..9d47e2fe5 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -262,11 +262,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); setModel (mProxyModel); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); -#else - horizontalHeader()->setResizeMode (QHeaderView::Interactive); -#endif verticalHeader()->hide(); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -675,7 +671,7 @@ void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) { std::string modifierString = setting->getKey().substr (6); - Qt::KeyboardModifiers modifiers = 0; + Qt::KeyboardModifiers modifiers; if (modifierString=="-s") modifiers = Qt::ShiftModifier; diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index be601d3b5..719b5c336 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,17 +15,13 @@ set(GAME_HEADER engine.hpp ) -if (BULLET_USE_DOUBLES) - add_definitions(-DBT_USE_DOUBLE_PRECISION) -endif() - source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) add_openmw_dir (mwinput @@ -45,7 +41,7 @@ add_openmw_dir (mwgui itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview - draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation + draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours ) add_openmw_dir (mwdialogue @@ -71,7 +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 + cellpreloader datetimemanager ) add_openmw_dir (mwphysics @@ -90,7 +86,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects + character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects spellabsorption linkedeffects ) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7a0f7a25c..c6f26dd0a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,7 @@ #include "engine.hpp" #include +#include #include @@ -186,7 +187,7 @@ bool OMW::Engine::frame(float frametime) osg::Timer_t afterWorldTick = osg::Timer::instance()->tick(); // update GUI - mEnvironment.getWindowManager()->onFrame(frametime); + mEnvironment.getWindowManager()->update(frametime); unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); osg::Stats* stats = mViewer->getViewerStats(); @@ -208,12 +209,14 @@ bool OMW::Engine::frame(float frametime) if (stats->collectStats("resource")) { + stats->setAttribute(frameNumber, "FrameNumber", frameNumber); + mResourceSystem->reportStats(frameNumber, stats); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); - mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats); + mEnvironment.reportStats(frameNumber, *stats); } } @@ -758,6 +761,14 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } + std::ofstream stats; + if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + { + stats.open(path, std::ios_base::out); + if (!stats) + Log(Debug::Warning) << "Failed to open file for stats: " << path; + } + // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; @@ -789,6 +800,12 @@ void OMW::Engine::go() simulationTime += dt; } + if (stats) + { + const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + mViewer->getViewerStats()->report(stats, frameNumber); + } + mEnvironment.limitFrameRate(frameTimer.time_s()); } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 2ecab1c4c..e25762f32 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -53,6 +53,8 @@ namespace MWBase virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; + virtual bool inJournal (const std::string& topicId, const std::string& infoId) = 0; + virtual void addTopic (const std::string& topic) = 0; virtual void addChoice (const std::string& text,int choice) = 0; @@ -68,7 +70,14 @@ namespace MWBase virtual void goodbyeSelected() = 0; virtual void questionAnswered (int answer, ResponseCallback* callback) = 0; + enum TopicType + { + Specific = 1, + Exhausted = 2 + }; + virtual std::list getAvailableTopics() = 0; + virtual int getTopicFlag(const std::string&) = 0; virtual bool checkServiceRefused (ResponseCallback* callback) = 0; diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 2d8ce6790..c70debda1 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -14,7 +14,6 @@ #include "windowmanager.hpp" #include "statemanager.hpp" - MWBase::Environment *MWBase::Environment::sThis = 0; MWBase::Environment::Environment() @@ -200,3 +199,8 @@ const MWBase::Environment& MWBase::Environment::get() return *sThis; } +void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const +{ + mMechanicsManager->reportStats(frameNumber, stats); + mWorld->reportStats(frameNumber, stats); +} diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index fbca7e63a..80e6a6243 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -1,6 +1,11 @@ #ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H +namespace osg +{ + class Stats; +} + namespace MWBase { class World; @@ -21,85 +26,84 @@ namespace MWBase /// the set* functions. class Environment { + static Environment *sThis; - static Environment *sThis; - - World *mWorld; - SoundManager *mSoundManager; - ScriptManager *mScriptManager; - WindowManager *mWindowManager; - MechanicsManager *mMechanicsManager; - DialogueManager *mDialogueManager; - Journal *mJournal; - InputManager *mInputManager; - StateManager *mStateManager; - float mFrameDuration; - float mFrameRateLimit; - - Environment (const Environment&); - ///< not implemented + World *mWorld; + SoundManager *mSoundManager; + ScriptManager *mScriptManager; + WindowManager *mWindowManager; + MechanicsManager *mMechanicsManager; + DialogueManager *mDialogueManager; + Journal *mJournal; + InputManager *mInputManager; + StateManager *mStateManager; + float mFrameDuration; + float mFrameRateLimit; - Environment& operator= (const Environment&); - ///< not implemented + Environment (const Environment&); + ///< not implemented - public: + Environment& operator= (const Environment&); + ///< not implemented - Environment(); + public: - ~Environment(); + Environment(); - void setWorld (World *world); + ~Environment(); - void setSoundManager (SoundManager *soundManager); + void setWorld (World *world); - void setScriptManager (MWBase::ScriptManager *scriptManager); + void setSoundManager (SoundManager *soundManager); - void setWindowManager (WindowManager *windowManager); + void setScriptManager (MWBase::ScriptManager *scriptManager); - void setMechanicsManager (MechanicsManager *mechanicsManager); + void setWindowManager (WindowManager *windowManager); - void setDialogueManager (DialogueManager *dialogueManager); + void setMechanicsManager (MechanicsManager *mechanicsManager); - void setJournal (Journal *journal); + void setDialogueManager (DialogueManager *dialogueManager); - void setInputManager (InputManager *inputManager); + void setJournal (Journal *journal); - void setStateManager (StateManager *stateManager); + void setInputManager (InputManager *inputManager); - void setFrameDuration (float duration); - ///< Set length of current frame in seconds. + void setStateManager (StateManager *stateManager); - void setFrameRateLimit(float frameRateLimit); - float getFrameRateLimit() const; - void limitFrameRate(double dt) const; + void setFrameDuration (float duration); + ///< Set length of current frame in seconds. - World *getWorld() const; + void setFrameRateLimit(float frameRateLimit); + float getFrameRateLimit() const; + void limitFrameRate(double dt) const; - SoundManager *getSoundManager() const; + World *getWorld() const; - ScriptManager *getScriptManager() const; + SoundManager *getSoundManager() const; - WindowManager *getWindowManager() const; + ScriptManager *getScriptManager() const; - MechanicsManager *getMechanicsManager() const; + WindowManager *getWindowManager() const; - DialogueManager *getDialogueManager() const; + MechanicsManager *getMechanicsManager() const; - Journal *getJournal() const; + DialogueManager *getDialogueManager() const; - InputManager* getInputManager() const; + Journal *getJournal() const; + InputManager *getInputManager() const; - StateManager *getStateManager() const; + StateManager *getStateManager() const; - float getFrameDuration() const; + float getFrameDuration() const; - void cleanup(); - ///< Delete all mw*-subsystems. + void cleanup(); + ///< Delete all mw*-subsystems. - static const Environment& get(); - ///< Return instance of this class. + static const Environment& get(); + ///< Return instance of this class. + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; } diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 5ab9e1c04..857aee8ec 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -78,6 +78,8 @@ namespace MWBase virtual void resetIdleTime() = 0; virtual void executeAction(int action) = 0; + + virtual bool controlsDisabled() = 0; }; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 411f5fab1..c9ce43e01 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -7,10 +7,14 @@ #include #include +#include "../mwmechanics/actorutil.hpp" +// For MWMechanics::GreetingState + #include "../mwworld/ptr.hpp" namespace osg { + class Stats; class Vec3f; } @@ -269,6 +273,15 @@ namespace MWBase virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; + + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; + + virtual int getGreetingTimer(const MWWorld::Ptr& ptr) const = 0; + virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; + virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; + virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; + + virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 7887ba862..8abe87188 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -252,13 +252,7 @@ namespace MWBase /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton() = 0; - virtual void onFrame (float frameDuration) = 0; - - /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map getPlayerSkillValues() = 0; - virtual std::map getPlayerAttributeValues() = 0; - virtual SkillList getPlayerMinorSkills() = 0; - virtual SkillList getPlayerMajorSkills() = 0; + virtual void update (float duration) = 0; /** * Fetches a GMST string from the store, if there is no setting with the given diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f71267a06..516b5e0aa 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -23,6 +23,7 @@ namespace osg class Quat; class Image; class Node; + class Stats; class Transform; } @@ -38,6 +39,7 @@ namespace ESM struct Position; struct Cell; struct Class; + struct Creature; struct Potion; struct Spell; struct NPC; @@ -49,6 +51,7 @@ namespace ESM struct EffectList; struct CreatureLevList; struct ItemLevList; + struct TimeStamp; } namespace MWRender @@ -80,11 +83,6 @@ namespace MWWorld typedef std::vector > PtrMovementList; } -namespace MWPhysics -{ - class PhysicsSystem; -} - namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) @@ -214,24 +212,14 @@ namespace MWBase virtual void advanceTime (double hours, bool incremental = false) = 0; ///< Advance in-game time. - virtual void setHour (double hour) = 0; - ///< Set in-game time hour. - - virtual void setMonth (int month) = 0; - ///< Set in-game time month. - - virtual void setDay (int day) = 0; - ///< Set in-game time day. - - virtual int getDay() const = 0; - virtual int getMonth() const = 0; - virtual int getYear() const = 0; - virtual std::string getMonthName (int month = -1) const = 0; ///< Return name of month (-1: current month) virtual MWWorld::TimeStamp getTimeStamp() const = 0; - ///< Return current in-game time stamp. + ///< Return current in-game time and number of day since new game start. + + virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0; + ///< Return current in-game date and time. virtual bool toggleSky() = 0; ///< \return Resulting mode @@ -389,6 +377,14 @@ namespace MWBase ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record + virtual const ESM::Creature *createOverrideRecord (const ESM::Creature& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + virtual const ESM::NPC *createOverrideRecord (const ESM::NPC& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused) = 0; virtual void updatePhysics (float duration, bool paused) = 0; @@ -653,10 +649,10 @@ namespace MWBase /// @Return ESM::Weapon::Type enum describing the type of weapon currently drawn by the player. virtual int getActiveWeaponType(void) = 0; - - virtual MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; + + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index e649bba12..3f9bfb859 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -276,7 +276,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); int armorSkillType = getEquipmentSkill(ptr); - int armorSkill = actor.getClass().getSkill(actor, armorSkillType); + float armorSkill = actor.getClass().getSkill(actor, armorSkillType); const MWBase::World *world = MWBase::Environment::get().getWorld(); int iBaseArmorSkill = world->getStore().get().find("iBaseArmorSkill")->mValue.getInteger(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2ae1ab0f0..5d39c4747 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -521,23 +521,16 @@ namespace MWClass float Creature::getSpeed(const MWWorld::Ptr &ptr) const { - MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) return 0.f; const GMST& gmst = getGmst(); - float walkSpeed = gmst.fMinWalkSpeedCreature->mValue.getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() - * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); - const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); - - // The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp) - float runSpeed = walkSpeed; - float moveSpeed; if(getEncumbrance(ptr) > getCapacity(ptr)) @@ -554,23 +547,14 @@ namespace MWClass moveSpeed = flySpeed; } else if(world->isSwimming(ptr)) - { - float swimSpeed = walkSpeed; - if(running) - swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * - gmst.fSwimRunAthleticsMult->mValue.getFloat(); - moveSpeed = swimSpeed; - } - else if(running) - moveSpeed = runSpeed; + moveSpeed = getSwimSpeed(ptr); else - moveSpeed = walkSpeed; - if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) - moveSpeed *= 0.75f; + moveSpeed = getWalkSpeed(ptr); - moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor; + const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); + if (movementSettings.mIsStrafing) + moveSpeed *= 0.75f; + moveSpeed *= movementSettings.mSpeedFactor; return moveSpeed; } @@ -619,7 +603,7 @@ namespace MWClass float Creature::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - return static_cast(stats.getAttribute(ESM::Attribute::Strength).getModified() * 5); + return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5; } int Creature::getServices(const MWWorld::ConstPtr &actor) const @@ -759,7 +743,7 @@ namespace MWClass throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } - int Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const + float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -823,6 +807,12 @@ namespace MWClass return; } + if (ptr.getRefData().getCount() <= 0) + { + state.mHasCustomState = false; + return; + } + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->writeState (creatureState.mInventory); @@ -892,4 +882,36 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); scale *= ref->mBase->mScale; } + + void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + { + MWMechanics::setBaseAISetting(id, setting, value); + } + + float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const + { + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const GMST& gmst = getGmst(); + + return gmst.fMinWalkSpeedCreature->mValue.getFloat() + + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); + } + + float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const + { + return getWalkSpeed(ptr); + } + + float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const + { + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const GMST& gmst = getGmst(); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); + + return getWalkSpeed(ptr) + * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) + * (gmst.fSwimRunBase->mValue.getFloat() + + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); + } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 3cc31bda0..7c5e5fc9b 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -108,7 +108,7 @@ namespace MWClass virtual bool canSwim (const MWWorld::ConstPtr &ptr) const; virtual bool canWalk (const MWWorld::ConstPtr &ptr) const; - virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; + virtual float getSkill(const MWWorld::Ptr &ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; @@ -129,6 +129,14 @@ namespace MWClass virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh + + virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; + + float getRunSpeed(const MWWorld::Ptr& ptr) const final; + + float getSwimSpeed(const MWWorld::Ptr& ptr) const final; }; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index bd61131bf..a007ad115 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -125,7 +125,7 @@ namespace MWClass } MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - int alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); + float alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 510f0abd7..f442f2138 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -128,8 +128,8 @@ namespace } // initial health - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); + float strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); + float endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); int multiplier = 3; @@ -796,7 +796,22 @@ namespace MWClass MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); - if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) + bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + // If there's no item in the carried left slot or if it is not a shield redistribute the hit. + if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft) + { + if (Misc::Rng::rollDice(2) == 0) + hitslot = MWWorld::InventoryStore::Slot_Cuirass; + else + hitslot = MWWorld::InventoryStore::Slot_LeftPauldron; + armorslot = inv.getSlot(hitslot); + if (armorslot != inv.end()) + { + armor = *armorslot; + hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + } + } + if (hasArmor) { if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition { @@ -976,16 +991,6 @@ namespace MWClass bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); - float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* - (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); - walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; - walkSpeed = std::max(0.0f, walkSpeed); - if(sneaking) - walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); - - float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * - gmst.fAthleticsRunBonus->mValue.getFloat() + gmst.fBaseRunMultiplier->mValue.getFloat()); - float moveSpeed; if(getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; @@ -1000,26 +1005,19 @@ namespace MWClass moveSpeed = flySpeed; } else if (swimming) - { - float swimSpeed = walkSpeed; - if(running) - swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics)* - gmst.fSwimRunAthleticsMult->mValue.getFloat(); - moveSpeed = swimSpeed; - } + moveSpeed = getSwimSpeed(ptr); else if (running && !sneaking) - moveSpeed = runSpeed; + moveSpeed = getRunSpeed(ptr); else - moveSpeed = walkSpeed; - if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) - moveSpeed *= 0.75f; + moveSpeed = getWalkSpeed(ptr); if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); - moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor; + const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); + if (movementSettings.mIsStrafing) + moveSpeed *= 0.75f; + moveSpeed *= movementSettings.mSpeedFactor; return moveSpeed; } @@ -1040,7 +1038,7 @@ namespace MWClass gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * (1.0f - Npc::getNormalizedEncumbrance(ptr)); - float a = static_cast(getSkill(ptr, ESM::Skill::Acrobatics)); + float a = getSkill(ptr, ESM::Skill::Acrobatics); float b = 0.0f; if(a > 50.0f) { @@ -1165,7 +1163,7 @@ namespace MWClass float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - int unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); + float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); float ratings[MWWorld::InventoryStore::Slots]; for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) @@ -1312,7 +1310,7 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const + float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const { return getNpcStats(ptr).getSkill(skill).getModified(); } @@ -1356,6 +1354,12 @@ namespace MWClass return; } + if (ptr.getRefData().getCount() <= 0) + { + state.mHasCustomState = false; + return; + } + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); ESM::NpcState& npcState = state.asNpcState(); customData.mInventoryStore.writeState (npcState.mInventory); @@ -1466,4 +1470,61 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->getFactionRank(); } + + void Npc::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + { + MWMechanics::setBaseAISetting(id, setting, value); + } + + float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const + { + const GMST& gmst = getGmst(); + const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); + const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); + const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + + float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + + 0.01f * npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); + walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; + walkSpeed = std::max(0.0f, walkSpeed); + if(sneaking) + walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); + + return walkSpeed; + } + + float Npc::getRunSpeed(const MWWorld::Ptr& ptr) const + { + const GMST& gmst = getGmst(); + return getWalkSpeed(ptr) + * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() + + gmst.fBaseRunMultiplier->mValue.getFloat()); + } + + float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const + { + const GMST& gmst = getGmst(); + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); + const MWMechanics::MagicEffects& mageffects = npcdata->mNpcStats.getMagicEffects(); + const bool swimming = world->isSwimming(ptr); + const bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); + const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run) + && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); + + float swimSpeed; + + if (running) + swimSpeed = getRunSpeed(ptr); + else + swimSpeed = getWalkSpeed(ptr); + + swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); + swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat(); + + return swimSpeed; + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index c3b971ea0..b1c91420d 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -129,7 +129,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; + virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; @@ -164,6 +164,14 @@ namespace MWClass virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; virtual int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const; + + virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; + + float getRunSpeed(const MWWorld::Ptr& ptr) const final; + + float getSwimSpeed(const MWWorld::Ptr& ptr) const final; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 21bc067d7..3e0cbbad2 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -228,6 +228,30 @@ namespace MWDialogue } } + bool DialogueManager::inJournal (const std::string& topicId, const std::string& infoId) + { + const MWDialogue::Topic *topicHistory = nullptr; + MWBase::Journal *journal = MWBase::Environment::get().getJournal(); + for (auto it = journal->topicBegin(); it != journal->topicEnd(); ++it) + { + if (it->first == topicId) + { + topicHistory = &it->second; + break; + } + } + + if (!topicHistory) + return false; + + for(const auto& topic : *topicHistory) + { + if (topic.mInfoId == infoId) + return true; + } + return false; + } + void DialogueManager::executeTopic (const std::string& topic, ResponseCallback* callback) { Filter filter (mActor, mChoice, mTalkedTo); @@ -300,22 +324,34 @@ namespace MWDialogue mActorKnownTopics.clear(); - const MWWorld::Store &dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get(); Filter filter (mActor, -1, mTalkedTo); - for (MWWorld::Store::iterator iter = dialogs.begin(); iter != dialogs.end(); ++iter) + for (const auto& dialog : dialogs) { - if (iter->mType == ESM::Dialogue::Topic) + if (dialog.mType == ESM::Dialogue::Topic) { - if (filter.responseAvailable (*iter)) + const auto* answer = filter.search(dialog, true); + auto topicId = Misc::StringUtils::lowerCase(dialog.mId); + + if (answer != nullptr) { - mActorKnownTopics.insert (iter->mId); + int flag = 0; + if(!inJournal(topicId, answer->mId)) + { + // Does this dialogue contains some actor-specific answer? + if (answer->mActor == mActor.getCellRef().getRefId()) + flag |= MWBase::DialogueManager::TopicType::Specific; + } + else + flag |= MWBase::DialogueManager::TopicType::Exhausted; + mActorKnownTopics.insert (dialog.mId); + mActorKnownTopicsFlag[dialog.mId] = flag; } + } } - } std::list DialogueManager::getAvailableTopics() @@ -336,6 +372,11 @@ namespace MWDialogue return keywordList; } + int DialogueManager::getTopicFlag(const std::string& topicId) + { + return mActorKnownTopicsFlag[topicId]; + } + void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) { if(!mIsInChoice) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index fad086183..23dd68e91 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ namespace MWDialogue ModFactionReactionMap mChangedFactionReaction; std::set mActorKnownTopics; + std::unordered_map mActorKnownTopicsFlag; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; @@ -71,6 +73,9 @@ namespace MWDialogue virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback); std::list getAvailableTopics(); + int getTopicFlag(const std::string& topicId) final; + + bool inJournal (const std::string& topicId, const std::string& infoId) final; virtual void addTopic (const std::string& topic); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 042ccb019..a3c326ab8 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -681,15 +681,3 @@ std::vector MWDialogue::Filter::list (const ESM::Dialogue return infos; } - -bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const -{ - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); - iter!=dialogue.mInfo.end(); ++iter) - { - if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) - return true; - } - - return false; -} diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 4e2ebe6e5..d2747d59a 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -66,9 +66,6 @@ namespace MWDialogue const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. - - bool responseAvailable (const ESM::Dialogue& dialogue) const; - ///< Does a matching response exist? (disposition is ignored for this check) }; } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 44376b87a..54e1a69a4 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -105,23 +105,32 @@ namespace MWGui mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; + + // Setup player stats + for (int i = 0; i < ESM::Attribute::Length; ++i) + mPlayerAttributes.emplace(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue()); + + for (int i = 0; i < ESM::Skill::Length; ++i) + mPlayerSkillValues.emplace(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue()); } void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { - if (mReviewDialog) + static const char *ids[] = { - static const char *ids[] = - { - "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", - "AttribVal6", "AttribVal7", "AttribVal8", - 0 - }; + "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", + "AttribVal5", "AttribVal6", "AttribVal7", "AttribVal8", 0 + }; - for (int i=0; ids[i]; ++i) + for (int i=0; ids[i]; ++i) + { + if (ids[i]==id) { - if (ids[i]==id) - mReviewDialog->setAttribute(ESM::Attribute::AttributeID(i), value); + mPlayerAttributes[static_cast(i)] = value; + if (mReviewDialog) + mReviewDialog->setAttribute(static_cast(i), value); + + break; } } } @@ -147,6 +156,7 @@ namespace MWGui void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { + mPlayerSkillValues[parSkill] = value; if (mReviewDialog) mReviewDialog->setSkillValue(parSkill, value); } @@ -155,6 +165,9 @@ namespace MWGui { if (mReviewDialog) mReviewDialog->configureSkills(major, minor); + + mPlayerMajorSkills = major; + mPlayerMinorSkills = minor; } void CharacterCreation::onFrame(float duration) @@ -269,31 +282,21 @@ namespace MWGui mReviewDialog->setClass(*playerClass); mReviewDialog->setBirthSign(player.getBirthSign()); - { - MWWorld::Ptr playerPtr = MWMechanics::getPlayer(); - const MWMechanics::CreatureStats& stats = playerPtr.getClass().getCreatureStats(playerPtr); - - mReviewDialog->setHealth ( stats.getHealth() ); - mReviewDialog->setMagicka( stats.getMagicka() ); - mReviewDialog->setFatigue( stats.getFatigue() ); - } + MWWorld::Ptr playerPtr = MWMechanics::getPlayer(); + const MWMechanics::CreatureStats& stats = playerPtr.getClass().getCreatureStats(playerPtr); + mReviewDialog->setHealth(stats.getHealth()); + mReviewDialog->setMagicka(stats.getMagicka()); + mReviewDialog->setFatigue(stats.getFatigue()); + for (auto& attributePair : mPlayerAttributes) { - std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); - for (auto& attributePair : attributes) - { - mReviewDialog->setAttribute(static_cast (attributePair.first), attributePair.second); - } + mReviewDialog->setAttribute(static_cast (attributePair.first), attributePair.second); } - + for (auto& skillPair : mPlayerSkillValues) { - std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); - for (auto& skillPair : skills) - { - mReviewDialog->setSkillValue(static_cast (skillPair.first), skillPair.second); - } - mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); + mReviewDialog->setSkillValue(static_cast (skillPair.first), skillPair.second); } + mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills); mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index d5739c3d0..4f6296785 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwmechanics/stat.hpp" @@ -56,6 +57,10 @@ namespace MWGui osg::Group* mParent; Resource::ResourceSystem* mResourceSystem; + SkillList mPlayerMajorSkills, mPlayerMinorSkills; + std::map mPlayerAttributes; + std::map mPlayerSkillValues; + //Dialogs TextInputDialog* mNameDialog; RaceDialog* mRaceDialog; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index daabfaa40..6f3fb9e91 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -343,6 +343,7 @@ namespace MWGui mTopicsList->adjustSize(); updateHistory(); + updateTopicFormat(); mCurrentWindowSize = _sender->getSize(); } @@ -452,7 +453,6 @@ namespace MWGui setTitle(mPtr.getClass().getName(mPtr)); updateTopics(); - updateTopicsPane(); // force update for new services updateDisposition(); restock(); @@ -495,8 +495,6 @@ namespace MWGui return; mIsCompanion = isCompanion(); mKeywords = keyWords; - - updateTopicsPane(); } void DialogueWindow::updateTopicsPane() @@ -546,15 +544,16 @@ namespace MWGui mTopicsList->addSeparator(); - for(std::string& keyword : mKeywords) + for(const auto& keyword : mKeywords) { + std::string topicId = Misc::StringUtils::lowerCase(keyword); mTopicsList->addItem(keyword); Topic* t = new Topic(keyword); t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); - mTopicLinks[Misc::StringUtils::lowerCase(keyword)] = t; + mTopicLinks[topicId] = t; - mKeywordSearch.seed(Misc::StringUtils::lowerCase(keyword), intptr_t(t)); + mKeywordSearch.seed(topicId, intptr_t(t)); } mTopicsList->adjustSize(); @@ -740,9 +739,31 @@ namespace MWGui updateHistory(); } + void DialogueWindow::updateTopicFormat() + { + if (!Settings::Manager::getBool("color topic enable", "GUI")) + return; + + std::string specialColour = Settings::Manager::getString("color topic specific", "GUI"); + std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI"); + + for (const std::string& keyword : mKeywords) + { + int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(keyword); + MyGUI::Button* button = mTopicsList->getItemWidget(keyword); + + if (!specialColour.empty() && flag & MWBase::DialogueManager::TopicType::Specific) + button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(specialColour)); + else if (!oldColour.empty() && flag & MWBase::DialogueManager::TopicType::Exhausted) + button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(oldColour)); + } + } + void DialogueWindow::updateTopics() { setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()); + updateTopicsPane(); + updateTopicFormat(); } bool DialogueWindow::isCompanion() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 2c3fb1a44..d9c26ef20 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -186,6 +186,8 @@ namespace MWGui std::unique_ptr mCallback; std::unique_ptr mGreetingCallback; + + void updateTopicFormat(); }; } #endif diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index cce199f1d..7a506013e 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -166,9 +166,7 @@ namespace MWGui getWidget(mCrosshair, "Crosshair"); - int mapSize = std::max(1, Settings::Manager::getInt("local map hud widget size", "Map")); - int cellDistance = std::max(1, Settings::Manager::getInt("local map cell distance", "Map")); - LocalMapBase::init(mMinimap, mCompass, mapSize, cellDistance); + LocalMapBase::init(mMinimap, mCompass); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 9c8fbfb17..67124884f 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -81,6 +81,12 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); + // We should not worsen corprus when in prison + for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) + { + spell.second.mNextWorsening += mDays * 24; + } + std::set skills; for (int day=0; day& gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index eb165254a..86f92db6f 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -157,7 +157,7 @@ namespace MWGui mAttributeValues[i]->setEnabled(true); availableAttributes++; - int mult = pcStats.getLevelupAttributeMultiplier (i); + float mult = pcStats.getLevelupAttributeMultiplier (i); mult = std::min(mult, 100-pcStats.getAttribute(i).getBase()); text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult)); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index dcfe723f7..521b88f29 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -29,9 +30,9 @@ namespace MWGui { - LoadingScreen::LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer) + LoadingScreen::LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer) : WindowBase("openmw_loading_screen.layout") - , mVFS(vfs) + , mResourceSystem(resourceSystem) , mViewer(viewer) , mTargetFrameRate(120.0) , mLastWallpaperChangeTime(0.0) @@ -39,6 +40,7 @@ namespace MWGui , mLoadingOnTime(0.0) , mImportantLabel(false) , mVisible(false) + , mNestedLoadingCount(0) , mProgress(0) , mShowWallpaper(true) { @@ -64,9 +66,9 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - const std::map& index = mVFS->getIndex(); + const std::map& index = mResourceSystem->getVFS()->getIndex(); std::string pattern = "Splash/"; - mVFS->normalizeFilename(pattern); + mResourceSystem->getVFS()->normalizeFilename(pattern); /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; @@ -162,15 +164,21 @@ namespace MWGui void LoadingScreen::loadingOn(bool visible) { - mLoadingOnTime = mTimer.time_m(); // Early-out if already on - if (mMainWidget->getVisible()) + if (mNestedLoadingCount++ > 0 && mMainWidget->getVisible()) return; + mLoadingOnTime = mTimer.time_m(); + // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); + if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { + mOldIcoMin = ico->getMinimumTimeAvailableForGLCompileAndDeletePerFrame(); + mOldIcoMax = ico->getMaximumNumOfObjectsToCompilePerFrame(); + } + mVisible = visible; mLoadingBox->setVisible(mVisible); setVisible(true); @@ -194,6 +202,8 @@ namespace MWGui void LoadingScreen::loadingOff() { + if (--mNestedLoadingCount > 0) + return; mLoadingBox->setVisible(true); // restore if (mLastRenderTime < mLoadingOnTime) @@ -215,6 +225,12 @@ namespace MWGui //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); + if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) + { + ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(mOldIcoMin); + ico->setMaximumNumOfObjectsToCompilePerFrame(mOldIcoMax); + } + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); } @@ -336,7 +352,13 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true, true); - //osg::Timer timer; + mResourceSystem->reportStats(mViewer->getFrameStamp()->getFrameNumber(), mViewer->getViewerStats()); + if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) + { + ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f/getTargetFrameRate()); + ico->setMaximumNumOfObjectsToCompilePerFrame(1000); + } + // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() @@ -344,10 +366,6 @@ namespace MWGui mViewer->updateTraversal(); mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - //std::cout << "frame took " << timer.time_m() << std::endl; - - //if (mViewer->getIncrementalCompileOperation()) - //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index c054f3bbd..b3e2534ce 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -20,9 +20,9 @@ namespace osg class Texture2D; } -namespace VFS +namespace Resource { - class Manager; + class ResourceSystem; } namespace MWGui @@ -32,7 +32,7 @@ namespace MWGui class LoadingScreen : public WindowBase, public Loading::Listener { public: - LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer); + LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer); virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details @@ -53,7 +53,7 @@ namespace MWGui void setupCopyFramebufferToTextureCallback(); - const VFS::Manager* mVFS; + Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mViewer; double mTargetFrameRate; @@ -66,10 +66,13 @@ namespace MWGui bool mImportantLabel; bool mVisible; + int mNestedLoadingCount; size_t mProgress; bool mShowWallpaper; + float mOldIcoMin = 0.f; + unsigned int mOldIcoMax = 0; MyGUI::Widget* mLoadingBox; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index e7e2aa581..351358ffe 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -183,13 +183,13 @@ namespace MWGui mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize, int cellDistance) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass) { mLocalMap = widget; mCompass = compass; - mMapWidgetSize = mapWidgetSize; - mCellDistance = cellDistance; - mNumCells = cellDistance * 2 + 1; + mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); + mCellDistance = Constants::CellGridRadius; + mNumCells = mCellDistance * 2 + 1; mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); @@ -701,9 +701,7 @@ namespace MWGui mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - int mapSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); - int cellDistance = std::max(1, Settings::Manager::getInt("local map cell distance", "Map")); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal, mapSize, cellDistance); + LocalMapBase::init(mLocalMap, mPlayerArrowLocal); mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 206998770..7b86f7617 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -72,7 +72,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, int cellDistance); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 2a167be2d..b4de5cb50 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -22,7 +22,7 @@ namespace MWGui { MWWorld::Ptr player = MWMechanics::getPlayer(); mSourceModel = sourceModel; - int chance = player.getClass().getSkill(player, ESM::Skill::Sneak); + float chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index f85bfc8d3..c4d608443 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -132,6 +132,13 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } + void SaveGameDialog::onClose() + { + mSaveList->setIndexSelected(MyGUI::ITEM_NONE); + + WindowModal::onClose(); + } + void SaveGameDialog::onOpen() { WindowModal::onOpen(); diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index a9915ee9d..e2c41af70 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -20,6 +20,7 @@ namespace MWGui SaveGameDialog(); virtual void onOpen(); + virtual void onClose(); void setLoadOrSave(bool load); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index b3358bdec..5a3e4955b 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -163,7 +163,7 @@ namespace MWGui for (int i=0; ids[i]; ++i) if (ids[i]==id) { - setText (id, std::to_string(value.getModified())); + setText (id, std::to_string(static_cast(value.getModified()))); MyGUI::TextBox* box; getWidget(box, id); @@ -624,10 +624,10 @@ namespace MWGui text += "\n"; - if (rankData.mSkill1 > 0) - text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mSkill1); - if (rankData.mSkill2 > 0) - text += " #{sand} #{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mSkill2); + if (rankData.mPrimarySkill > 0) + text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mPrimarySkill); + if (rankData.mFavouredSkill > 0) + text += " #{sand} #{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mFavouredSkill); } } diff --git a/apps/openmw/mwgui/textcolours.cpp b/apps/openmw/mwgui/textcolours.cpp new file mode 100644 index 000000000..77afa5e26 --- /dev/null +++ b/apps/openmw/mwgui/textcolours.cpp @@ -0,0 +1,36 @@ +#include "textcolours.hpp" + +#include + +#include + +namespace MWGui +{ + MyGUI::Colour getTextColour(const std::string& type) + { + return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); + } + + void TextColours::loadColours() + { + header = getTextColour("header"); + normal = getTextColour("normal"); + notify = getTextColour("notify"); + + link = getTextColour("link"); + linkOver = getTextColour("link_over"); + linkPressed = getTextColour("link_pressed"); + + answer = getTextColour("answer"); + answerOver = getTextColour("answer_over"); + answerPressed = getTextColour("answer_pressed"); + + journalLink = getTextColour("journal_link"); + journalLinkOver = getTextColour("journal_link_over"); + journalLinkPressed = getTextColour("journal_link_pressed"); + + journalTopic = getTextColour("journal_topic"); + journalTopicOver = getTextColour("journal_topic_over"); + journalTopicPressed = getTextColour("journal_topic_pressed"); + } +} diff --git a/apps/openmw/mwgui/textcolours.hpp b/apps/openmw/mwgui/textcolours.hpp index 3fa55654b..83bc1d3f5 100644 --- a/apps/openmw/mwgui/textcolours.hpp +++ b/apps/openmw/mwgui/textcolours.hpp @@ -5,14 +5,12 @@ namespace MWGui { - struct TextColours { MyGUI::Colour header; MyGUI::Colour normal; MyGUI::Colour notify; - MyGUI::Colour link; MyGUI::Colour linkOver; MyGUI::Colour linkPressed; @@ -28,6 +26,9 @@ namespace MWGui MyGUI::Colour journalTopic; MyGUI::Colour journalTopicOver; MyGUI::Colour journalTopicPressed; + + public: + void loadColours(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index acc2ef72a..e4e4bae5a 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -74,11 +74,11 @@ namespace MWGui mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); // NPC can train you in his best 3 skills - std::vector< std::pair > skills; + std::vector< std::pair > skills; for (int i=0; i= 13) hour -= 12; if (hour == 0) hour = 12; - std::string dateTimeText = - MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getDay ()) + " " - + month + " (#{sDay} " + MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getTimeStamp ().getDay()) - + ") " + MyGUI::utility::toString(hour) + " " + (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); - + ESM::EpochTimeStamp currentDate = MWBase::Environment::get().getWorld()->getEpochTimeStamp(); + int daysPassed = MWBase::Environment::get().getWorld()->getTimeStamp().getDay(); + std::string formattedHour = pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"; + std::string dateTimeText = Misc::StringUtils::format("%i %s (#{sDay} %i) %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour); mDateTimeText->setCaptionWithReplacing (dateTimeText); } diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 0568efeb4..74076641a 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -443,6 +443,9 @@ namespace MWGui // constant effects have no duration and no target if (!mEffectParams.mIsConstant) { + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) + mEffectParams.mDuration = std::max(1, mEffectParams.mDuration); + if (mEffectParams.mDuration > 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) { spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + MyGUI::utility::toString(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 46024f333..016a1ac49 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -122,19 +122,8 @@ #include "../mwvr/vrgui.hpp" #endif -namespace -{ - - MyGUI::Colour getTextColour(const std::string& type) - { - return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); - } - -} - namespace MWGui { - WindowManager::WindowManager( SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, @@ -188,12 +177,6 @@ namespace MWGui , mCursorActive(false) , mVideoEnabled(false) , mPlayerBounty(-1) - , mPlayerName() - , mPlayerRaceId() - , mPlayerAttributes() - , mPlayerMajorSkills() - , mPlayerMinorSkills() - , mPlayerSkillValues() , mGui(nullptr) , mGuiModes() , mCursorManager(nullptr) @@ -204,7 +187,6 @@ namespace MWGui , mRestAllowed(true) , mShowOwned(0) , mEncoding(encoding) - , mFontHeight(16) , mVersionDescription(versionDescription) , mWindowVisible(true) { @@ -246,13 +228,6 @@ namespace MWGui SpellView::registerComponents(); Gui::registerAllWidgets(); - int fontSize = Settings::Manager::getInt("font size", "GUI"); - fontSize = std::min(std::max(12, fontSize), 20); - mFontHeight = fontSize; - - MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); - MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = newDelegate(this, &WindowManager::loadFontDelegate); - MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); @@ -269,7 +244,7 @@ namespace MWGui mKeyboardNavigation->setEnabled(keyboardNav); Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); - mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); + mLoadingScreen = new LoadingScreen(mResourceSystem, mViewer); mWindows.push_back(mLoadingScreen); //set up the hardware cursor manager @@ -313,94 +288,6 @@ namespace MWGui Settings::Manager::getFloat("contrast", "Video")); } - void WindowManager::loadFontDelegate(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) - { - MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); - bool createCopy = false; - while (resourceNode.next("Resource")) - { - std::string type, name; - resourceNode->findAttribute("type", type); - resourceNode->findAttribute("name", name); - - if (name.empty()) - continue; - - if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) - { - createCopy = true; - - // For TrueType fonts we should override Size and Resolution properties - // to allow to configure font size via config file, without need to edit XML files. - // Also we should take UI scaling factor in account. - int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); - resolution = std::min(960, std::max(48, resolution)); - - float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - resolution *= uiScale; - - MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); - resolutionNode->addAttribute("key", "Resolution"); - resolutionNode->addAttribute("value", std::to_string(resolution)); - - MyGUI::xml::ElementPtr sizeNode = resourceNode->createChild("Property"); - sizeNode->addAttribute("key", "Size"); - sizeNode->addAttribute("value", std::to_string(mFontHeight)); - } - else if (Misc::StringUtils::ciEqual(type, "ResourceSkin") || - Misc::StringUtils::ciEqual(type, "AutoSizedResourceSkin")) - { - // We should adjust line height for MyGUI widgets depending on font size - MyGUI::xml::ElementPtr heightNode = resourceNode->createChild("Property"); - heightNode->addAttribute("key", "HeightLine"); - heightNode->addAttribute("value", std::to_string(mFontHeight+2)); - } - } - - MyGUI::ResourceManager::getInstance().loadFromXmlNode(_node, _file, _version); - - if (createCopy) - { - MyGUI::xml::ElementPtr copy = _node->createCopy(); - - MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); - while (copyFont.next("Resource")) - { - std::string type, name; - copyFont->findAttribute("type", type); - copyFont->findAttribute("name", name); - - if (name.empty()) - continue; - - if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) - { - // Since the journal and books use the custom scaling factor depending on resolution, - // setup separate fonts with different Resolution to fit these windows. - // These fonts have an internal prefix. - int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); - resolution = std::min(960, std::max(48, resolution)); - - float currentX = Settings::Manager::getInt("resolution x", "Video"); - float currentY = Settings::Manager::getInt("resolution y", "Video"); - // TODO: read size from openmw_layout.xml - float heightScale = (currentY / 520); - float widthScale = (currentX / 600); - float uiScale = std::min(widthScale, heightScale); - resolution *= uiScale; - - MyGUI::xml::ElementPtr resolutionNode = copyFont->createChild("Property"); - resolutionNode->addAttribute("key", "Resolution"); - resolutionNode->addAttribute("value", std::to_string(resolution)); - - copyFont->setAttribute("name", "Journalbook " + name); - } - } - - MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); - } - } - void WindowManager::loadUserFonts() { mFontLoader->loadTrueTypeFonts(); @@ -412,26 +299,7 @@ namespace MWGui int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; - mTextColours.header = getTextColour("header"); - mTextColours.normal = getTextColour("normal"); - mTextColours.notify = getTextColour("notify"); - - mTextColours.link = getTextColour("link"); - mTextColours.linkOver = getTextColour("link_over"); - mTextColours.linkPressed = getTextColour("link_pressed"); - - mTextColours.answer = getTextColour("answer"); - mTextColours.answerOver = getTextColour("answer_over"); - mTextColours.answerPressed = getTextColour("answer_pressed"); - - mTextColours.journalLink = getTextColour("journal_link"); - mTextColours.journalLinkOver = getTextColour("journal_link_over"); - mTextColours.journalLinkPressed = getTextColour("journal_link_pressed"); - - mTextColours.journalTopic = getTextColour("journal_topic"); - mTextColours.journalTopicOver = getTextColour("journal_topic_over"); - mTextColours.journalTopicPressed = getTextColour("journal_topic_pressed"); - + mTextColours.loadColours(); mDragAndDrop = new DragAndDrop(); @@ -608,17 +476,6 @@ namespace MWGui mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); - // Setup player stats - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue())); - } - - for (int i = 0; i < ESM::Skill::Length; ++i) - { - mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue())); - } - updatePinnedWindows(); // Set up visibility @@ -627,7 +484,7 @@ namespace MWGui int WindowManager::getFontHeight() const { - return mFontHeight; + return mFontLoader->getFontHeight(); } void WindowManager::setNewGame(bool newgame) @@ -648,7 +505,6 @@ namespace MWGui { mKeyboardNavigation.reset(); - MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); @@ -811,40 +667,14 @@ namespace MWGui { mStatsWindow->setValue (id, value); mCharGen->setValue(id, value); - - static const char *ids[] = - { - "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", - "AttribVal6", "AttribVal7", "AttribVal8" - }; - static ESM::Attribute::AttributeID attributes[] = - { - ESM::Attribute::Strength, - ESM::Attribute::Intelligence, - ESM::Attribute::Willpower, - ESM::Attribute::Agility, - ESM::Attribute::Speed, - ESM::Attribute::Endurance, - ESM::Attribute::Personality, - ESM::Attribute::Luck - }; - for (size_t i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i) - { - if (id != ids[i]) - continue; - mPlayerAttributes[attributes[i]] = value; - break; - } } - void WindowManager::setValue (int parSkill, const MWMechanics::SkillValue& value) { /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we /// allow custom skills. mStatsWindow->setValue(static_cast (parSkill), value); mCharGen->setValue(static_cast (parSkill), value); - mPlayerSkillValues[parSkill] = value; } void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicStat& value) @@ -857,10 +687,6 @@ namespace MWGui void WindowManager::setValue (const std::string& id, const std::string& value) { mStatsWindow->setValue (id, value); - if (id=="name") - mPlayerName = value; - else if (id=="race") - mPlayerRaceId = value; } void WindowManager::setValue (const std::string& id, int value) @@ -882,8 +708,6 @@ namespace MWGui { mStatsWindow->configureSkills (major, minor); mCharGen->configureSkills(major, minor); - mPlayerMajorSkills = major; - mPlayerMinorSkills = minor; } void WindowManager::updateSkillArea() @@ -1021,7 +845,7 @@ namespace MWGui mHud->setPlayerPos(x, y, u, v); } - void WindowManager::onFrame (float frameDuration) + void WindowManager::update (float frameDuration) { bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame; @@ -1663,26 +1487,6 @@ namespace MWGui return mGuiModes.back(); } - std::map WindowManager::getPlayerSkillValues() - { - return mPlayerSkillValues; - } - - std::map WindowManager::getPlayerAttributeValues() - { - return mPlayerAttributes; - } - - WindowManager::SkillList WindowManager::getPlayerMinorSkills() - { - return mPlayerMinorSkills; - } - - WindowManager::SkillList WindowManager::getPlayerMajorSkills() - { - return mPlayerMajorSkills; - } - void WindowManager::disallowMouse() { mInputBlocker->setVisible (true); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9056636e7..e758d52f2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -282,13 +282,7 @@ namespace MWGui virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) - virtual void onFrame (float frameDuration); - - /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map getPlayerSkillValues(); - virtual std::map getPlayerAttributeValues(); - virtual SkillList getPlayerMinorSkills(); - virtual SkillList getPlayerMajorSkills(); + virtual void update (float duration); /** * Fetches a GMST string from the store, if there is no setting with the given @@ -420,8 +414,6 @@ namespace MWGui MWWorld::Ptr mSelectedEnchantItem; MWWorld::Ptr mSelectedWeapon; - void loadFontDelegate(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); - std::vector mCurrentModals; // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). @@ -478,14 +470,6 @@ namespace MWGui void setCursorVisible(bool visible); - /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - // Various stats about player as needed by window manager - std::string mPlayerName; - std::string mPlayerRaceId; - std::map mPlayerAttributes; - SkillList mPlayerMajorSkills, mPlayerMinorSkills; - std::map mPlayerSkillValues; - MyGUI::Gui *mGui; // Gui struct GuiModeState @@ -535,8 +519,6 @@ namespace MWGui ToUTF8::FromType mEncoding; - int mFontHeight; - std::string mVersionDescription; bool mWindowVisible; diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index a71c5b31a..3b1f587ce 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -36,7 +36,7 @@ namespace MWInput , mSneakToggleShortcutTimer(0.f) , mGamepadZoom(0) , mGamepadGuiCursorEnabled(true) - , mControlsDisabled(false) + , mGuiCursorEnabled(true) , mJoystickLastUsed(false) , mSneakGamepadShortcut(false) , mGamepadPreviewMode(false) @@ -83,9 +83,8 @@ namespace MWInput } } - bool ControllerManager::update(float dt, bool disableControls) + bool ControllerManager::update(float dt) { - mControlsDisabled = disableControls; mGamepadPreviewMode = mActionManager->getPreviewDelay() == 1.f; if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) @@ -232,7 +231,7 @@ namespace MWInput auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); - if (!mControlsDisabled) + if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) mBindingsManager->controllerButtonPressed(deviceID, arg); } @@ -244,7 +243,7 @@ namespace MWInput return; } - if (!mJoystickEnabled || mControlsDisabled) + if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; mJoystickLastUsed = true; @@ -275,7 +274,7 @@ namespace MWInput void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) { - if (!mJoystickEnabled || mControlsDisabled) + if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; mJoystickLastUsed = true; diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 6b9546b0c..3c1a2ee6e 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -23,7 +23,7 @@ namespace MWInput virtual ~ControllerManager() = default; - bool update(float dt, bool disableControls); + bool update(float dt); virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg); virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg); @@ -56,9 +56,8 @@ namespace MWInput float mSneakToggleShortcutTimer; float mGamepadZoom; bool mGamepadGuiCursorEnabled; - bool mControlsDisabled; - bool mJoystickLastUsed; bool mGuiCursorEnabled; + bool mJoystickLastUsed; bool mSneakGamepadShortcut; bool mGamepadPreviewMode; }; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 48e1581be..79541fbe4 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -30,7 +30,7 @@ namespace MWInput osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab) - : mGrabCursor(Settings::Manager::getBool("grab cursor", "Input")) + : mControlsDisabled(false) { mInputWrapper = new SDLUtil::InputWrapper(window, viewer, grab); mInputWrapper->setWindowEventCallback(MWBase::Environment::get().getWindowManager()); @@ -80,47 +80,25 @@ namespace MWInput mActionManager->setAttemptJump(jumping); } - void InputManager::updateCursorMode() - { - bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) - && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); - - bool wasRelative = mInputWrapper->getMouseRelative(); - bool isRelative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); - - // don't keep the pointer away from the window edge in gui mode - // stop using raw mouse motions and switch to system cursor movements - mInputWrapper->setMouseRelative(isRelative); - - //we let the mouse escape in the main menu - mInputWrapper->setGrabPointer(grab && (mGrabCursor || isRelative)); - - //we switched to non-relative mode, move our cursor to where the in-game - //cursor is - if (!isRelative && wasRelative != isRelative) - { - mMouseManager->warpMouse(); - } - } - void InputManager::update(float dt, bool disableControls, bool disableEvents) { + mControlsDisabled = disableControls; + mInputWrapper->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); mInputWrapper->capture(disableEvents); - mKeyboardManager->setControlsDisabled(disableControls); if (disableControls) { - updateCursorMode(); + mMouseManager->updateCursorMode(); return; } mBindingsManager->update(dt); - updateCursorMode(); + mMouseManager->updateCursorMode(); - bool controllerMove = mControllerManager->update(dt, disableControls); - mMouseManager->update(dt, disableControls); + bool controllerMove = mControllerManager->update(dt); + mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); } @@ -151,12 +129,6 @@ namespace MWInput void InputManager::processChangedSettings(const Settings::CategorySettingVector& changed) { - for (const auto& setting : changed) - { - if (setting.first == "Input" && setting.second == "grab cursor") - mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); - } - mMouseManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed); } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 18cf0d729..a45c7486c 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -96,14 +96,15 @@ namespace MWInput void applyHapticsLeftHand(float intensity) override {}; void applyHapticsRightHand(float intensity) override {}; + + virtual bool controlsDisabled() { return mControlsDisabled; } + protected: void convertMousePosForMyGUI(int& x, int& y); void handleGuiArrowKey(int action); - void updateCursorMode(); - //void quickKey(int index); //void showQuickKeysMenu(); @@ -112,7 +113,7 @@ namespace MWInput SDLUtil::InputWrapper* mInputWrapper; - bool mGrabCursor; + bool mControlsDisabled; ControlSwitch* mControlSwitch; diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index 20115155e..db047a342 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -18,7 +18,6 @@ namespace MWInput { KeyboardManager::KeyboardManager(BindingsManager* bindingsManager) : mBindingsManager(bindingsManager) - , mControlsDisabled(false) { } @@ -53,10 +52,11 @@ namespace MWInput if (arg.repeat) return; - if (!mControlsDisabled && !consumed) + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + if (!input->controlsDisabled() && !consumed) mBindingsManager->keyPressed(arg); - MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); + input->setJoystickLastUsed(false); } void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) diff --git a/apps/openmw/mwinput/keyboardmanager.hpp b/apps/openmw/mwinput/keyboardmanager.hpp index ae473be51..b7027220f 100644 --- a/apps/openmw/mwinput/keyboardmanager.hpp +++ b/apps/openmw/mwinput/keyboardmanager.hpp @@ -19,12 +19,8 @@ namespace MWInput virtual void keyPressed(const SDL_KeyboardEvent &arg); virtual void keyReleased(const SDL_KeyboardEvent &arg); - void setControlsDisabled(bool disabled) { mControlsDisabled = disabled; } - private: BindingsManager* mBindingsManager; - - bool mControlsDisabled; }; } #endif diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index b19111b6b..3c18e48d4 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -24,8 +24,9 @@ namespace MWInput MouseManager::MouseManager(BindingsManager* bindingsManager, SDLUtil::InputWrapper* inputWrapper, SDL_Window* window) : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) - , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) - , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) + , mGrabCursor(Settings::Manager::getBool("grab cursor", "Input")) + , mCameraSensitivity(Settings::Manager::getFloat("camera sensitivity", "Input")) + , mCameraYMultiplier(Settings::Manager::getFloat("camera y multiplier", "Input")) , mBindingsManager(bindingsManager) , mInputWrapper(inputWrapper) , mInvUiScalingFactor(1.f) @@ -33,7 +34,6 @@ namespace MWInput , mGuiCursorY(0) , mMouseWheel(0) , mMouseLookEnabled(false) - , mControlsDisabled(false) , mGuiCursorEnabled(true) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); @@ -59,6 +59,9 @@ namespace MWInput if (setting.first == "Input" && setting.second == "camera sensitivity") mCameraSensitivity = Settings::Manager::getFloat("camera sensitivity", "Input"); + + if (setting.first == "Input" && setting.second == "grab cursor") + mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); } } @@ -88,7 +91,7 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->setCursorActive(true); } - if (mMouseLookEnabled && !mControlsDisabled) + if (mMouseLookEnabled && !input->controlsDisabled()) { float x = arg.xrel * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f; float y = arg.yrel * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f; @@ -136,10 +139,11 @@ namespace MWInput void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent &arg) { - if (mBindingsManager->isDetectingBindingState() || !mControlsDisabled) + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) mBindingsManager->mouseWheelMoved(arg); - MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); + input->setJoystickLastUsed(false); } void MouseManager::mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) @@ -169,10 +173,31 @@ namespace MWInput mBindingsManager->mousePressed(arg, id); } - void MouseManager::update(float dt, bool disableControls) + void MouseManager::updateCursorMode() { - mControlsDisabled = disableControls; + bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) + && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); + + bool wasRelative = mInputWrapper->getMouseRelative(); + bool isRelative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); + + // don't keep the pointer away from the window edge in gui mode + // stop using raw mouse motions and switch to system cursor movements + mInputWrapper->setMouseRelative(isRelative); + //we let the mouse escape in the main menu + mInputWrapper->setGrabPointer(grab && (mGrabCursor || isRelative)); + + //we switched to non-relative mode, move our cursor to where the in-game + //cursor is + if (!isRelative && wasRelative != isRelative) + { + warpMouse(); + } + } + + void MouseManager::update(float dt) + { if (!mMouseLookEnabled) return; diff --git a/apps/openmw/mwinput/mousemanager.hpp b/apps/openmw/mwinput/mousemanager.hpp index 3c5a8a018..c416fe2ad 100644 --- a/apps/openmw/mwinput/mousemanager.hpp +++ b/apps/openmw/mwinput/mousemanager.hpp @@ -20,7 +20,8 @@ namespace MWInput virtual ~MouseManager() = default; - void update(float dt, bool disableControls); + void updateCursorMode(); + void update(float dt); virtual void mouseMoved(const SDLUtil::MouseMotionEvent &arg); virtual void mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id); @@ -44,6 +45,7 @@ namespace MWInput private: bool mInvertX; bool mInvertY; + bool mGrabCursor; float mCameraSensitivity; float mCameraYMultiplier; @@ -55,7 +57,6 @@ namespace MWInput float mGuiCursorY; int mMouseWheel; bool mMouseLookEnabled; - bool mControlsDisabled; bool mGuiCursorEnabled; }; } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 57b009689..2ccffcc91 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -12,14 +12,12 @@ namespace MWMechanics { - void ActiveSpells::update() const + void ActiveSpells::update(float duration) const { bool rebuild = false; - MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); - // Erase no longer active spells and effects - if (mLastUpdate!=now) + if (duration > 0) { TContainer::iterator iter (mSpells.begin()); while (iter!=mSpells.end()) @@ -31,24 +29,35 @@ namespace MWMechanics } else { + bool interrupt = false; std::vector& effects = iter->second.mEffects; for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) { - MWWorld::TimeStamp start = iter->second.mTimeStamp; - MWWorld::TimeStamp end = start + static_cast(effectIt->mDuration)*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - if (end <= now) + if (effectIt->mTimeLeft <= 0) { - effectIt = effects.erase(effectIt); rebuild = true; + + // Note: it we expire a Corprus effect, we should remove the whole spell. + if (effectIt->mEffectId == ESM::MagicEffect::Corprus) + { + iter = mSpells.erase (iter); + interrupt = true; + break; + } + + effectIt = effects.erase(effectIt); } else + { + effectIt->mTimeLeft -= duration; ++effectIt; + } } - ++iter; + + if (!interrupt) + ++iter; } } - - mLastUpdate = now; } if (mSpellsChanged) @@ -63,24 +72,15 @@ namespace MWMechanics void ActiveSpells::rebuildEffects() const { - MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); - mEffects = MagicEffects(); for (TIterator iter (begin()); iter!=end(); ++iter) { - const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - const std::vector& effects = iter->second.mEffects; for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { - double duration = effectIt->mDuration; - MWWorld::TimeStamp end = start; - end += duration * - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - - if (end>now) + if (effectIt->mTimeLeft > 0) mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); } } @@ -88,12 +88,11 @@ namespace MWMechanics ActiveSpells::ActiveSpells() : mSpellsChanged (false) - , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} const MagicEffects& ActiveSpells::getMagicEffects() const { - update(); + update(0.f); return mEffects; } @@ -116,19 +115,14 @@ namespace MWMechanics for (std::vector::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { - if (iter->mDuration > duration) - duration = iter->mDuration; + if (iter->mTimeLeft > duration) + duration = iter->mTimeLeft; } - double scaledDuration = duration * - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - - double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp; - - if (usedUp>=scaledDuration) + if (duration < 0) return 0; - return scaledDuration-usedUp; + return duration; } bool ActiveSpells::isSpellActive(const std::string& id) const @@ -152,7 +146,6 @@ namespace MWMechanics TContainer::iterator it(mSpells.find(id)); ActiveSpellParams params; - params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mEffects = effects; params.mDisplayName = displayName; params.mCasterActorId = casterActorId; @@ -211,19 +204,15 @@ namespace MWMechanics { for (TContainer::const_iterator it = begin(); it != end(); ++it) { - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end(); ++effectIt) { std::string name = it->second.mDisplayName; - float remainingTime = effectIt->mDuration + - static_cast(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration); + visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); } } } @@ -301,6 +290,31 @@ namespace MWMechanics mSpellsChanged = true; } + void ActiveSpells::purgeCorprusDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + bool hasCorprusEffect = false; + for (std::vector::iterator effectIt = iter->second.mEffects.begin(); + effectIt != iter->second.mEffects.end();++effectIt) + { + if (effectIt->mEffectId == ESM::MagicEffect::Corprus) + { + hasCorprusEffect = true; + break; + } + } + + if (hasCorprusEffect) + { + mSpells.erase(iter++); + mSpellsChanged = true; + } + else + ++iter; + } + } + void ActiveSpells::clear() { mSpells.clear(); @@ -316,7 +330,6 @@ namespace MWMechanics params.mEffects = it->second.mEffects; params.mCasterActorId = it->second.mCasterActorId; params.mDisplayName = it->second.mDisplayName; - params.mTimeStamp = it->second.mTimeStamp.toEsm(); state.mSpells.insert (std::make_pair(it->first, params)); } @@ -331,7 +344,6 @@ namespace MWMechanics params.mEffects = it->second.mEffects; params.mCasterActorId = it->second.mCasterActorId; params.mDisplayName = it->second.mDisplayName; - params.mTimeStamp = MWWorld::TimeStamp(it->second.mTimeStamp); mSpells.insert (std::make_pair(it->first, params)); mSpellsChanged = true; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index a19c8a51d..9a1783bc9 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -44,15 +44,14 @@ namespace MWMechanics TIterator end() const; + void update(float duration) const; + private: mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; - mutable MWWorld::TimeStamp mLastUpdate; - void update() const; - void rebuildEffects() const; /// Add any effects that are in "from" and not in "addTo" to "addTo" @@ -100,6 +99,8 @@ namespace MWMechanics bool isSpellActive (const std::string& id) const; ///< case insensitive + void purgeCorprusDisease(); + const MagicEffects& getMagicEffects() const; void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp index f7c6b7f1c..a5c55633a 100644 --- a/apps/openmw/mwmechanics/actor.cpp +++ b/apps/openmw/mwmechanics/actor.cpp @@ -18,4 +18,44 @@ namespace MWMechanics { return mCharacterController.get(); } + + int Actor::getGreetingTimer() const + { + return mGreetingTimer; + } + + void Actor::setGreetingTimer(int timer) + { + mGreetingTimer = timer; + } + + float Actor::getAngleToPlayer() const + { + return mTargetAngleRadians; + } + + void Actor::setAngleToPlayer(float angle) + { + mTargetAngleRadians = angle; + } + + GreetingState Actor::getGreetingState() const + { + return mGreetingState; + } + + void Actor::setGreetingState(GreetingState state) + { + mGreetingState = state; + } + + bool Actor::isTurningToPlayer() const + { + return mIsTurningToPlayer; + } + + void Actor::setTurningToPlayer(bool turning) + { + mIsTurningToPlayer = turning; + } } diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index 119527b64..287ca420f 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -3,6 +3,8 @@ #include +#include "../mwmechanics/actorutil.hpp" + namespace MWRender { class Animation; @@ -27,8 +29,24 @@ namespace MWMechanics CharacterController* getCharacterController(); + int getGreetingTimer() const; + void setGreetingTimer(int timer); + + float getAngleToPlayer() const; + void setAngleToPlayer(float angle); + + GreetingState getGreetingState() const; + void setGreetingState(GreetingState state); + + bool isTurningToPlayer() const; + void setTurningToPlayer(bool turning); + private: std::unique_ptr mCharacterController; + int mGreetingTimer{0}; + float mTargetAngleRadians{0.f}; + GreetingState mGreetingState{Greet_None}; + bool mIsTurningToPlayer{false}; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 9cfada560..b82d08964 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -112,11 +112,11 @@ void adjustCommandedActor (const MWWorld::Ptr& actor) bool hasCommandPackage = false; - std::list::const_iterator it; - for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + auto it = stats.getAiSequence().begin(); + for (; it != stats.getAiSequence().end(); ++it) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && - static_cast(*it)->isCommanded()) + if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && + static_cast(it->get())->isCommanded()) { hasCommandPackage = true; break; @@ -132,7 +132,7 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); health = 0.1f * endurance; float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); @@ -176,6 +176,49 @@ namespace MWMechanics } }; + class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor + { + std::string mSpellId; + + public: + GetCurrentMagnitudes(const std::string& spellId) + : mSpellId(spellId) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (magnitude <= 0) + return; + + if (sourceId != mSpellId) + return; + + mMagnitudes.push_back(std::make_pair(key, magnitude)); + } + + std::vector> mMagnitudes; + }; + + class GetCorprusSpells : public MWMechanics::EffectSourceVisitor + { + + public: + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (key.mId != ESM::MagicEffect::Corprus) + return; + + mSpells.push_back(sourceId); + } + + std::vector mSpells; + }; + class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -410,7 +453,7 @@ namespace MWMechanics return; const MWMechanics::AiSequence& seq = stats.getAiSequence(); - if (seq.isInCombat() || seq.hasPackage(AiPackage::TypeIdFollow) || seq.hasPackage(AiPackage::TypeIdEscort)) + if (seq.isInCombat() || seq.hasPackage(AiPackageTypeId::Follow) || seq.hasPackage(AiPackageTypeId::Escort)) return; const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3()); @@ -419,7 +462,7 @@ namespace MWMechanics if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000) return; - // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. + // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); @@ -435,9 +478,9 @@ namespace MWMechanics CreatureStats &stats = actor.getClass().getCreatureStats(actor); MWMechanics::AiSequence& seq = stats.getAiSequence(); - if (!seq.isEmpty() && seq.getActivePackage()->useVariableSpeed()) + if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed()) { - osg::Vec3f targetPos = seq.getActivePackage()->getDestination(); + osg::Vec3f targetPos = seq.getActivePackage().getDestination(); osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); float distance = (targetPos - actorPos).length(); if (distance < DECELERATE_DISTANCE) @@ -447,22 +490,22 @@ namespace MWMechanics actor.getClass().getMovementSettings(actor).mSpeedFactor = newSpeedFactor; } - void Actors::updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly) + void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) { if (!actor.getClass().isActor() || actor == getPlayer()) return; CreatureStats &stats = actor.getClass().getCreatureStats(actor); const MWMechanics::AiSequence& seq = stats.getAiSequence(); - int packageId = seq.getTypeId(); + const auto packageId = seq.getTypeId(); if (seq.isInCombat() || MWBase::Environment::get().getWorld()->isSwimming(actor) || - (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1)) + (packageId != AiPackageTypeId::Wander && packageId != AiPackageTypeId::Travel && packageId != AiPackageTypeId::None)) { - stats.setTurningToPlayer(false); - stats.setGreetingTimer(0); - stats.setGreetingState(Greet_None); + actorState.setTurningToPlayer(false); + actorState.setGreetingTimer(0); + actorState.setGreetingState(Greet_None); return; } @@ -471,14 +514,14 @@ namespace MWMechanics osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); osg::Vec3f dir = playerPos - actorPos; - if (stats.isTurningToPlayer()) + if (actorState.isTurningToPlayer()) { // Reduce the turning animation glitch by using a *HUGE* value of // epsilon... TODO: a proper fix might be in either the physics or the // animation subsystem - if (zTurn(actor, stats.getAngleToPlayer(), osg::DegreesToRadians(5.f))) + if (zTurn(actor, actorState.getAngleToPlayer(), osg::DegreesToRadians(5.f))) { - stats.setTurningToPlayer(false); + actorState.setTurningToPlayer(false); // An original engine launches an endless idle2 when an actor greets player. playAnimationGroup (actor, "idle2", 0, std::numeric_limits::max(), false); } @@ -493,8 +536,8 @@ namespace MWMechanics float helloDistance = static_cast(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); - int greetingTimer = stats.getGreetingTimer(); - GreetingState greetingState = stats.getGreetingState(); + int greetingTimer = actorState.getGreetingTimer(); + GreetingState greetingState = actorState.getGreetingState(); if (greetingState == Greet_None) { if ((playerPos - actorPos).length2() <= helloDistance*helloDistance && @@ -516,7 +559,7 @@ namespace MWMechanics greetingTimer++; if (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor)) - turnActorToFacePlayer(actor, dir); + turnActorToFacePlayer(actor, actorState, dir); if (greetingTimer >= GREETING_COOLDOWN) { @@ -532,20 +575,19 @@ namespace MWMechanics greetingState = Greet_None; } - stats.setGreetingTimer(greetingTimer); - stats.setGreetingState(greetingState); + actorState.setGreetingTimer(greetingTimer); + actorState.setGreetingState(greetingState); } - void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir) + void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[0] = 0; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - if (!stats.isTurningToPlayer()) + if (!actorState.isTurningToPlayer()) { - stats.setAngleToPlayer(std::atan2(dir.x(), dir.y())); - stats.setTurningToPlayer(true); + actorState.setAngleToPlayer(std::atan2(dir.x(), dir.y())); + actorState.setTurningToPlayer(true); } } @@ -605,7 +647,7 @@ namespace MWMechanics bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); // If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them - // Doesn't apply for player followers/escorters + // Doesn't apply for player followers/escorters if (!aggressive && !isPlayerFollowerOrEscorter) { // Check that actor2 is in combat with actor1 @@ -674,7 +716,7 @@ namespace MWMechanics return; bool followerOrEscorter = false; - for (const AiPackage* package : creatureStats2.getAiSequence()) + for (const auto& package : creatureStats2.getAiSequence()) { // The follow package must be first or have nothing but combat before it if (package->sideWithTarget()) @@ -682,7 +724,7 @@ namespace MWMechanics followerOrEscorter = true; break; } - else if (package->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) break; } if (!followerOrEscorter) @@ -723,7 +765,7 @@ namespace MWMechanics { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); + float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); float base = 1.f; if (ptr == getPlayer()) @@ -774,6 +816,9 @@ namespace MWMechanics if (visitor.mRemainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + if(timeScale == 0.0) + timeScale = 1; + restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); } else if (visitor.mRemainingTime == -1) @@ -799,7 +844,7 @@ namespace MWMechanics float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); - int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); if (normalizedEncumbrance > 1) @@ -826,7 +871,7 @@ namespace MWMechanics return; // Restore fatigue - int endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); + float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); @@ -940,21 +985,75 @@ namespace MWMechanics if (creatureStats.needToRecalcDynamicStats()) calculateDynamicStats(ptr); + if (ptr == getPlayer()) { - Spells & spells = creatureStats.getSpells(); - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + GetCorprusSpells getCorprusSpellsVisitor; + creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor); + creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor); + ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor); + std::vector corprusSpells = getCorprusSpellsVisitor.mSpells; + std::vector corprusSpellsToRemove; + + for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it) { - if (spells.getCorprusSpells().find(it->first) != spells.getCorprusSpells().end()) + if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end()) { - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= spells.getCorprusSpells().at(it->first).mNextWorsening) - { - spells.worsenCorprus(it->first); + // Corprus effect expired, remove entry and restore stats. + MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first); + corprusSpellsToRemove.push_back(it->first); + corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); + continue; + } + + corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); - if (ptr == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening) + { + it->second.mNextWorsening += CorprusStats::sWorseningPeriod; + GetCurrentMagnitudes getMagnitudesVisitor (it->first); + creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor); + creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor); + ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor); + for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes) + { + if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute) + { + AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); + attr.damage(-effectMagnitude.second); + creatureStats.setAttribute(effectMagnitude.first.mArg, attr); + it->second.mWorsenings[effectMagnitude.first.mArg] = 0; + } + else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute) + { + AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); + int currentDamage = attr.getDamage(); + if (currentDamage >= 0) + it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage); + + it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second; + attr.damage(effectMagnitude.second); + creatureStats.setAttribute(effectMagnitude.first.mArg, attr); + } } + + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); } } + + for (std::string& oldCorprusSpell : corprusSpellsToRemove) + { + creatureStats.removeCorprusSpell(oldCorprusSpell); + } + + for (std::string& newCorprusSpell : corprusSpells) + { + CorprusStats corprus; + for (int i=0; igetTimeStamp() + CorprusStats::sWorseningPeriod; + + creatureStats.addCorprusSpell(newCorprusSpell, corprus); + } } // AI setting modifiers @@ -1160,7 +1259,7 @@ namespace MWMechanics if (!isPlayer && stats.getTimeToStartDrowning() < fHoldBreathTime / 2) { AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - if (seq.getTypeId() != AiPackage::TypeIdBreathe) //Only add it once + if (seq.getTypeId() != AiPackageTypeId::Breathe) //Only add it once seq.stack(AiBreathe(), ptr); } @@ -1311,7 +1410,7 @@ namespace MWMechanics if (player.getClass().getNpcStats(player).isWerewolf()) return; - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat() + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackageTypeId::Pursue && !creatureStats.getAiSequence().isInCombat() && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -1619,9 +1718,16 @@ namespace MWMechanics player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } - // For dead actors we need to remove looping spell particles + iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + + // For dead actors we need to update looping spell particles if (iter->first.getClass().getCreatureStats(iter->first).isDead()) + { + // They can be added during the death animation + if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()) + adjustMagicEffects(iter->first); ctrl->updateContinuousVfx(); + } else { bool cellChanged = world->hasCellChanged(); @@ -1667,7 +1773,7 @@ namespace MWMechanics // 3. Player character does not use headtracking in the 1st-person view if (!stats.getKnockedDown() && !stats.getAiSequence().isInCombat() && - !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue) && + !stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue) && !firstPersonPlayer) { for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) @@ -1690,7 +1796,7 @@ namespace MWMechanics if (isConscious(iter->first)) { stats.getAiSequence().execute(iter->first, *ctrl, duration); - updateGreetingState(iter->first, timerUpdateHello > 0); + updateGreetingState(iter->first, *iter->second, timerUpdateHello > 0); playIdleDialogue(iter->first); updateMovementSpeed(iter->first); } @@ -1734,7 +1840,7 @@ namespace MWMechanics if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed()) { MWMechanics::AiSequence& seq = stats.getAiSequence(); - alwaysActive = !seq.isEmpty() && seq.getActivePackage()->alwaysActive(); + alwaysActive = !seq.isEmpty() && seq.getActivePackage().alwaysActive(); } bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive; int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) @@ -1935,12 +2041,18 @@ namespace MWMechanics void Actors::rest(double hours, bool sleep) { - float duration = hours * 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + float duration = hours * 3600.f; + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + if (timeScale != 0.f) + duration /= timeScale; + const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + if (iter->first.getClass().getCreatureStats(iter->first).isDead()) continue; @@ -2150,7 +2262,7 @@ namespace MWMechanics // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them - for (const AiPackage* package : stats.getAiSequence()) + for (const auto& package : stats.getAiSequence()) { if (package->sideWithTarget() && !package->getTarget().isEmpty()) { @@ -2164,7 +2276,7 @@ namespace MWMechanics } break; } - else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander) + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) break; } } @@ -2184,13 +2296,13 @@ namespace MWMechanics if (stats.isDead()) continue; - // An actor counts as following if AiFollow is the current AiPackage, + // An actor counts as following if AiFollow is the current AiPackage, // or there are only Combat and Wander packages before the AiFollow package - for (const AiPackage* package : stats.getAiSequence()) + for (const auto& package : stats.getAiSequence()) { if (package->followTargetThroughDoors() && package->getTarget() == actor) list.push_back(iteratedActor); - else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander) + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) break; } } @@ -2249,14 +2361,14 @@ namespace MWMechanics // An actor counts as following if AiFollow is the current AiPackage, // or there are only Combat and Wander packages before the AiFollow package - for (AiPackage* package : stats.getAiSequence()) + for (const auto& package : stats.getAiSequence()) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { - list.push_back(static_cast(package)->getFollowIndex()); + list.push_back(static_cast(package.get())->getFollowIndex()); break; } - else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander) + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) break; } } @@ -2381,6 +2493,42 @@ namespace MWMechanics return ctrl->isAttackingOrSpell(); } + int Actors::getGreetingTimer(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return 0; + + return it->second->getGreetingTimer(); + } + + float Actors::getAngleToPlayer(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return 0.f; + + return it->second->getAngleToPlayer(); + } + + GreetingState Actors::getGreetingState(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return Greet_None; + + return it->second->getGreetingState(); + } + + bool Actors::isTurningToPlayer(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + + return it->second->isTurningToPlayer(); + } + void Actors::fastForwardAi() { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index eb705fd68..bd5a14c0d 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -7,6 +7,8 @@ #include #include +#include "../mwmechanics/actorutil.hpp" + namespace ESM { class ESMReader; @@ -70,6 +72,7 @@ namespace MWMechanics PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } + std::size_t size() const { return mActors.size(); } void notifyDied(const MWWorld::Ptr &actor); @@ -122,8 +125,8 @@ namespace MWMechanics void playIdleDialogue(const MWWorld::Ptr& actor); void updateMovementSpeed(const MWWorld::Ptr& actor); - void updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly); - void turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir); + void updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly); + void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir); void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); @@ -194,6 +197,11 @@ namespace MWMechanics bool isReadyToBlock(const MWWorld::Ptr& ptr) const; bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; + int getGreetingTimer(const MWWorld::Ptr& ptr) const; + float getAngleToPlayer(const MWWorld::Ptr& ptr) const; + GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; + bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; + private: void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 82a904799..cb77ef3ea 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,6 +1,16 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +#include "./creaturestats.hpp" + namespace MWWorld { class Ptr; @@ -8,9 +18,43 @@ namespace MWWorld namespace MWMechanics { + enum GreetingState + { + Greet_None, + Greet_InProgress, + Greet_Done + }; + MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); + + template + void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) + { + T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); + switch(setting) + { + case MWMechanics::CreatureStats::AiSetting::AI_Hello: + copy.mAiData.mHello = value; + break; + case MWMechanics::CreatureStats::AiSetting::AI_Fight: + copy.mAiData.mFight = value; + break; + case MWMechanics::CreatureStats::AiSetting::AI_Flee: + copy.mAiData.mFlee = value; + break; + case MWMechanics::CreatureStats::AiSetting::AI_Alarm: + copy.mAiData.mAlarm = value; + break; + default: + assert(0); + } + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + } + + template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); + template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); } #endif diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index c412a7ea1..b4ddf0c03 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -18,11 +18,6 @@ namespace MWMechanics { } - AiActivate *MWMechanics::AiActivate::clone() const - { - return new AiActivate(*this); - } - bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow @@ -49,11 +44,6 @@ namespace MWMechanics return false; } - int AiActivate::getTypeId() const - { - return TypeIdActivate; - } - void AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr activate(new ESM::AiSequence::AiActivate()); diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 8de4be69f..c53744e88 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include @@ -19,7 +19,7 @@ namespace MWMechanics { /// \brief Causes actor to walk to activatable object and activate it /** Will activate when close to object **/ - class AiActivate : public AiPackage + class AiActivate final : public TypedAiPackage { public: /// Constructor @@ -28,14 +28,14 @@ namespace MWMechanics AiActivate(const ESM::AiSequence::AiActivate* activate); - virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); - virtual int getTypeId() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Activate; } + + void writeState(ESM::AiSequence::AiSequence& sequence) const final; private: - std::string mObjectId; + const std::string mObjectId; }; } #endif // GAME_MWMECHANICS_AIACTIVATE_H diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index c476c9b57..47f72efce 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -16,7 +16,7 @@ static const int MAX_DIRECTIONS = 4; MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) -: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mDirection(0) +: mDuration(1), mDoorPtr(doorPtr), mDirection(0) { } @@ -63,7 +63,7 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { if(*it != getPlayer()) { //Not the player MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); - if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once + if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) { //Only add it once seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it); } } @@ -72,21 +72,6 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont return false; } -MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const -{ - return new AiAvoidDoor(*this); -} - -int MWMechanics::AiAvoidDoor::getTypeId() const -{ - return TypeIdAvoidDoor; -} - -unsigned int MWMechanics::AiAvoidDoor::getPriority() const -{ - return 2; -} - bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const { return (actorPos - mLastPos).length2() < 10 * 10; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 4c8be29eb..cd0718e2e 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIAVOIDDOOR_H #define GAME_MWMECHANICS_AIAVOIDDOOR_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include @@ -16,26 +16,28 @@ namespace MWMechanics /// \brief AiPackage to have an actor avoid an opening door /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it **/ - class AiAvoidDoor : public AiPackage + class AiAvoidDoor final : public TypedAiPackage { public: /// Avoid door until the door is fully open AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); - virtual AiAvoidDoor *clone() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::AvoidDoor; } - virtual int getTypeId() const; - - virtual unsigned int getPriority() const; - - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } private: float mDuration; - MWWorld::ConstPtr mDoorPtr; + const MWWorld::ConstPtr mDoorPtr; osg::Vec3f mLastPos; int mDirection; diff --git a/apps/openmw/mwmechanics/aibreathe.cpp b/apps/openmw/mwmechanics/aibreathe.cpp index 4955f683c..15251e125 100644 --- a/apps/openmw/mwmechanics/aibreathe.cpp +++ b/apps/openmw/mwmechanics/aibreathe.cpp @@ -11,12 +11,6 @@ #include "movement.hpp" #include "steering.hpp" -MWMechanics::AiBreathe::AiBreathe() -: AiPackage() -{ - -} - bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); @@ -37,18 +31,3 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro return true; } - -MWMechanics::AiBreathe *MWMechanics::AiBreathe::clone() const -{ - return new AiBreathe(*this); -} - -int MWMechanics::AiBreathe::getTypeId() const -{ - return TypeIdBreathe; -} - -unsigned int MWMechanics::AiBreathe::getPriority() const -{ - return 2; -} diff --git a/apps/openmw/mwmechanics/aibreathe.hpp b/apps/openmw/mwmechanics/aibreathe.hpp index 263ab8c2b..7e9ac69da 100644 --- a/apps/openmw/mwmechanics/aibreathe.hpp +++ b/apps/openmw/mwmechanics/aibreathe.hpp @@ -1,28 +1,27 @@ #ifndef GAME_MWMECHANICS_AIBREATHE_H #define GAME_MWMECHANICS_AIBREATHE_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" namespace MWMechanics { /// \brief AiPackage to have an actor resurface to breathe // The AI will go up if lesser than half breath left - class AiBreathe : public AiPackage + class AiBreathe final : public TypedAiPackage { public: - AiBreathe(); - - virtual AiBreathe *clone() const; - - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); - - virtual int getTypeId() const; - - virtual unsigned int getPriority() const; - - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; + + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Breathe; } + + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } }; } #endif - diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index b7c9bac3b..9ad7b4c56 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -10,17 +10,22 @@ #include "creaturestats.hpp" #include "steering.hpp" -MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell) - : mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(0) +namespace MWMechanics { - ActionSpell action = ActionSpell(spellId); - bool isRanged; - mDistance = action.getCombatRange(isRanged); + namespace + { + float getInitialDistance(const std::string& spellId) + { + ActionSpell action = ActionSpell(spellId); + bool isRanged; + return action.getCombatRange(isRanged); + } + } } -MWMechanics::AiPackage *MWMechanics::AiCast::clone() const +MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell) + : mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(getInitialDistance(spellId)) { - return new AiCast(*this); } bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration) @@ -84,13 +89,3 @@ MWWorld::Ptr MWMechanics::AiCast::getTarget() const return target; } - -int MWMechanics::AiCast::getTypeId() const -{ - return AiPackage::TypeIdCast; -} - -unsigned int MWMechanics::AiCast::getPriority() const -{ - return 3; -} diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 7128fe7a2..1175dccd2 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AICAST_H #define GAME_MWMECHANICS_AICAST_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" namespace MWWorld { @@ -11,29 +11,31 @@ namespace MWWorld namespace MWMechanics { /// AiPackage which makes an actor to cast given spell. - class AiCast : public AiPackage { + class AiCast final : public TypedAiPackage { public: AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false); - virtual AiPackage *clone() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Cast; } - virtual int getTypeId() const; + MWWorld::Ptr getTarget() const final; - virtual MWWorld::Ptr getTarget() const; - - virtual unsigned int getPriority() const; - - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 3; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } private: - std::string mTargetId; - std::string mSpellId; + const std::string mTargetId; + const std::string mSpellId; bool mCasting; - bool mManual; - float mDistance; + const bool mManual; + const float mDistance; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ee1b9cecd..a77539a1f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,6 +1,7 @@ #include "aicombat.hpp" #include +#include #include @@ -21,7 +22,6 @@ #include "movement.hpp" #include "character.hpp" #include "aicombataction.hpp" -#include "coordinateconverter.hpp" #include "actorutil.hpp" namespace @@ -302,7 +302,7 @@ namespace MWMechanics if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; - CoordinateConverter coords(storage.mCell->getCell()); + Misc::CoordinateConverter coords(storage.mCell->getCell()); osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); coords.toLocal(localPos); @@ -406,26 +406,11 @@ namespace MWMechanics } } - int AiCombat::getTypeId() const - { - return TypeIdCombat; - } - - unsigned int AiCombat::getPriority() const - { - return 1; - } - MWWorld::Ptr AiCombat::getTarget() const { return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } - AiCombat *MWMechanics::AiCombat::clone() const - { - return new AiCombat(*this); - } - void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr combat(new ESM::AiSequence::AiCombat()); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index f89e71678..ff8a0e081 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AICOMBAT_H #define GAME_MWMECHANICS_AICOMBAT_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include "../mwworld/cellstore.hpp" // for Doors @@ -91,7 +91,7 @@ namespace MWMechanics }; /// \brief Causes the actor to fight another actor - class AiCombat : public AiPackage + class AiCombat final : public TypedAiPackage { public: ///Constructor @@ -102,21 +102,23 @@ namespace MWMechanics void init(); - virtual AiCombat *clone() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Combat; } - virtual int getTypeId() const; - - virtual unsigned int getPriority() const; + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 1; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } ///Returns target ID - MWWorld::Ptr getTarget() const; - - virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + MWWorld::Ptr getTarget() const final; - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + void writeState(ESM::AiSequence::AiSequence &sequence) const final; private: /// Returns true if combat should end diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 03951d279..5dc1e44db 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -26,7 +26,6 @@ namespace MWMechanics , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; - mMaxDist = 450; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) @@ -35,30 +34,20 @@ namespace MWMechanics , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; - mMaxDist = 450; } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) - , mMaxDist(450) + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + , mDuration(escort->mRemainingDuration > 0) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = escort->mTargetId; mTargetActorId = escort->mTargetActorId; - // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. - // The exact value of mDuration only matters for repeating packages. - if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - mDuration = 1; - else - mDuration = 0; - } - - - AiEscort *MWMechanics::AiEscort::clone() const - { - return new AiEscort(*this); } bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) @@ -106,11 +95,6 @@ namespace MWMechanics return false; } - int AiEscort::getTypeId() const - { - return TypeIdEscort; - } - void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr escort(new ESM::AiSequence::AiEscort()); diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index e4319b425..edfd0ca3f 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIESCORT_H #define GAME_MWMECHANICS_AIESCORT_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include @@ -16,7 +16,7 @@ namespace AiSequence namespace MWMechanics { /// \brief AI Package to have an NPC lead the player to a specific point - class AiEscort : public AiPackage + class AiEscort final : public TypedAiPackage { public: /// Implementation of AiEscort @@ -30,33 +30,35 @@ namespace MWMechanics AiEscort(const ESM::AiSequence::AiEscort* escort); - virtual AiEscort *clone() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Escort; } - virtual int getTypeId() const; + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mSideWithTarget = true; + return options; + } - virtual bool useVariableSpeed() const { return true;} + void writeState(ESM::AiSequence::AiSequence &sequence) const final; - virtual bool sideWithTarget() const { return true; } + void fastForward(const MWWorld::Ptr& actor, AiState& state) final; - void writeState(ESM::AiSequence::AiSequence &sequence) const; - - void fastForward(const MWWorld::Ptr& actor, AiState& state); - - virtual osg::Vec3f getDestination() const { return osg::Vec3f(mX, mY, mZ); } + osg::Vec3f getDestination() const final { return osg::Vec3f(mX, mY, mZ); } private: - std::string mCellId; - float mX; - float mY; - float mZ; - float mMaxDist; - float mDuration; // In hours + const std::string mCellId; + const float mX; + const float mY; + const float mZ; + float mMaxDist = 450; + const float mDuration; // In hours float mRemainingDuration; // In hours - int mCellX; - int mCellY; + const int mCellX; + const int mCellY; }; } #endif diff --git a/apps/openmw/mwmechanics/aiface.cpp b/apps/openmw/mwmechanics/aiface.cpp index b99a8c1f4..17b18babc 100644 --- a/apps/openmw/mwmechanics/aiface.cpp +++ b/apps/openmw/mwmechanics/aiface.cpp @@ -9,23 +9,8 @@ MWMechanics::AiFace::AiFace(float targetX, float targetY) { } -MWMechanics::AiPackage *MWMechanics::AiFace::clone() const -{ - return new AiFace(*this); -} - bool MWMechanics::AiFace::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& /*characterController*/, MWMechanics::AiState& /*state*/, float /*duration*/) { osg::Vec3f dir = osg::Vec3f(mTargetX, mTargetY, 0) - actor.getRefData().getPosition().asVec3(); return zTurn(actor, std::atan2(dir.x(), dir.y()), osg::DegreesToRadians(3.f)); } - -int MWMechanics::AiFace::getTypeId() const -{ - return AiPackage::TypeIdFace; -} - -unsigned int MWMechanics::AiFace::getPriority() const -{ - return 2; -} diff --git a/apps/openmw/mwmechanics/aiface.hpp b/apps/openmw/mwmechanics/aiface.hpp index 099e5d237..a5ee67a14 100644 --- a/apps/openmw/mwmechanics/aiface.hpp +++ b/apps/openmw/mwmechanics/aiface.hpp @@ -1,28 +1,31 @@ #ifndef GAME_MWMECHANICS_AIFACE_H #define GAME_MWMECHANICS_AIFACE_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" namespace MWMechanics { /// AiPackage which makes an actor face a certain direction. - class AiFace : public AiPackage { + class AiFace final : public TypedAiPackage { public: AiFace(float targetX, float targetY); - virtual AiPackage *clone() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Face; } - virtual int getTypeId() const; - - virtual unsigned int getPriority() const; - - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } private: - float mTargetX, mTargetY; + const float mTargetX; + const float mTargetY; }; } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 09e8d0ecd..a9e43b3c3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -16,25 +16,24 @@ namespace MWMechanics { - int AiFollow::mFollowIndexCounter = 0; AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); @@ -42,7 +41,7 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, } AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); @@ -50,7 +49,8 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float d } AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) -: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) +: TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) +, mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); @@ -58,18 +58,18 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) + : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded)) + , mAlwaysFollow(follow->mAlwaysFollow) + // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + , mDuration(follow->mRemainingDuration) + , mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = follow->mTargetId; mTargetActorId = follow->mTargetActorId; - // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. - // The exact value of mDuration only matters for repeating packages. - if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - mDuration = 1; - else - mDuration = 0; } bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) @@ -201,19 +201,9 @@ std::string AiFollow::getFollowedActor() return mTargetActorRefId; } -AiFollow *MWMechanics::AiFollow::clone() const -{ - return new AiFollow(*this); -} - -int AiFollow::getTypeId() const -{ - return TypeIdFollow; -} - bool AiFollow::isCommanded() const { - return mCommanded; + return !mOptions.mShouldCancelPreviousAi; } void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const @@ -227,7 +217,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; - follow->mCommanded = mCommanded; + follow->mCommanded = isCommanded(); follow->mActive = mActive; ESM::AiSequence::AiPackageContainer package; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 24263bbc0..d0c2cec97 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIFOLLOW_H #define GAME_MWMECHANICS_AIFOLLOW_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include @@ -39,7 +39,7 @@ namespace MWMechanics /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely **/ - class AiFollow : public AiPackage + class AiFollow final : public TypedAiPackage { public: AiFollow(const std::string &actorId, float duration, float x, float y, float z); @@ -53,30 +53,31 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); - virtual bool sideWithTarget() const { return true; } - virtual bool followTargetThroughDoors() const { return true; } - virtual bool shouldCancelPreviousAi() const { return !mCommanded; } + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual AiFollow *clone() const; + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Follow; } - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); - - virtual int getTypeId() const; - - virtual bool useVariableSpeed() const { return true;} + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mSideWithTarget = true; + options.mFollowTargetThroughDoors = true; + return options; + } /// Returns the actor being followed std::string getFollowedActor(); - virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + void writeState (ESM::AiSequence::AiSequence& sequence) const final; bool isCommanded() const; int getFollowIndex() const; - void fastForward(const MWWorld::Ptr& actor, AiState& state); + void fastForward(const MWWorld::Ptr& actor, AiState& state) final; - virtual osg::Vec3f getDestination() const + osg::Vec3f getDestination() const final { MWWorld::Ptr target = getTarget(); if (target.isEmpty()) @@ -88,16 +89,15 @@ namespace MWMechanics private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ - bool mAlwaysFollow; - bool mCommanded; - float mDuration; // Hours + const bool mAlwaysFollow; + const float mDuration; // Hours float mRemainingDuration; // Hours - float mX; - float mY; - float mZ; - std::string mCellId; + const float mX; + const float mY; + const float mZ; + const std::string mCellId; bool mActive; // have we spotted the target? - int mFollowIndex; + const int mFollowIndex; static int mFollowIndexCounter; }; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 114e011ce..f7c07bde0 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -20,13 +21,12 @@ #include "movement.hpp" #include "steering.hpp" #include "actorutil.hpp" -#include "coordinateconverter.hpp" #include -MWMechanics::AiPackage::~AiPackage() {} - -MWMechanics::AiPackage::AiPackage() : +MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : + mTypeId(typeId), + mOptions(options), mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTargetActorRefId(""), mTargetActorId(-1), @@ -60,31 +60,6 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const return MWWorld::Ptr(); } -bool MWMechanics::AiPackage::sideWithTarget() const -{ - return false; -} - -bool MWMechanics::AiPackage::followTargetThroughDoors() const -{ - return false; -} - -bool MWMechanics::AiPackage::canCancel() const -{ - return true; -} - -bool MWMechanics::AiPackage::shouldCancelPreviousAi() const -{ - return true; -} - -bool MWMechanics::AiPackage::getRepeat() const -{ - return false; -} - void MWMechanics::AiPackage::reset() { // reset all members @@ -139,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), - pathfindingHalfExtents, getNavigatorFlags(actor)); + pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -241,7 +216,7 @@ namespace void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) { // note: AiWander currently does not open doors - if (getTypeId() == TypeIdWander) + if (getTypeId() == AiPackageTypeId::Wander) return; if (mPathFinder.getPathSize() == 0) @@ -366,10 +341,9 @@ bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) if (playerCell->isExterior()) { // get actor's distance from origin of center cell - CoordinateConverter(playerCell).toLocal(position); + Misc::CoordinateConverter(playerCell).toLocal(position); // currently assumes 3 x 3 grid for exterior cells, with player at center cell. - // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells // AI shuts down actors before they reach edges of 3 x 3 grid. const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; @@ -416,14 +390,38 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: const MWWorld::Class& actorClass = actor.getClass(); DetourNavigator::Flags result = DetourNavigator::Flag_none; - if (actorClass.isPureWaterCreature(actor) || (getTypeId() != TypeIdWander && actorClass.canSwim(actor))) + if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor))) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor)) result |= DetourNavigator::Flag_walk; - if (actorClass.isBipedal(actor) && getTypeId() != TypeIdWander) + if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; return result; } + +DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::Ptr& actor) const +{ + DetourNavigator::AreaCosts costs; + const DetourNavigator::Flags flags = getNavigatorFlags(actor); + const MWWorld::Class& actorClass = actor.getClass(); + + if (flags & DetourNavigator::Flag_swim) + costs.mWater = costs.mWater / actorClass.getSwimSpeed(actor); + + if (flags & DetourNavigator::Flag_walk) + { + float walkCost; + if (getTypeId() == AiPackageTypeId::Wander) + walkCost = 1.0 / actorClass.getWalkSpeed(actor); + else + walkCost = 1.0 / actorClass.getRunSpeed(actor); + costs.mDoor = costs.mDoor * walkCost; + costs.mPathgrid = costs.mPathgrid * walkCost; + costs.mGround = costs.mGround * walkCost; + } + + return costs; +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index ec0715e52..4201de5c8 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -1,11 +1,15 @@ #ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H +#include + #include +#include #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" +#include "aipackagetypeid.hpp" namespace MWWorld { @@ -33,34 +37,41 @@ namespace MWMechanics class AiPackage { public: - ///Enumerates the various AITypes available - enum TypeId { - TypeIdNone = -1, - TypeIdWander = 0, - TypeIdTravel = 1, - TypeIdEscort = 2, - TypeIdFollow = 3, - TypeIdActivate = 4, - - // These 5 are not really handled as Ai Packages in the MW engine - // For compatibility do *not* return these in the getCurrentAiPackage script function.. - TypeIdCombat = 5, - TypeIdPursue = 6, - TypeIdAvoidDoor = 7, - TypeIdFace = 8, - TypeIdBreathe = 9, - TypeIdInternalTravel = 10, - TypeIdCast = 11 + struct Options + { + unsigned int mPriority = 0; + bool mUseVariableSpeed = false; + bool mSideWithTarget = false; + bool mFollowTargetThroughDoors = false; + bool mCanCancel = true; + bool mShouldCancelPreviousAi = true; + bool mRepeat = false; + bool mAlwaysActive = false; + + constexpr Options withRepeat(bool value) + { + mRepeat = value; + return *this; + } + + constexpr Options withShouldCancelPreviousAi(bool value) + { + mShouldCancelPreviousAi = value; + return *this; + } }; - ///Default constructor - AiPackage(); + AiPackage(AiPackageTypeId typeId, const Options& options); + + virtual ~AiPackage() = default; - ///Default Deconstructor - virtual ~AiPackage(); + static constexpr Options makeDefaultOptions() + { + return Options{}; + } ///Clones the package - virtual AiPackage *clone() const = 0; + virtual std::unique_ptr clone() const = 0; /// Updates and runs the package (Should run every frame) /// \return Package completed? @@ -68,13 +79,13 @@ namespace MWMechanics /// Returns the TypeID of the AiPackage /// \see enum TypeId - virtual int getTypeId() const = 0; + AiPackageTypeId getTypeId() const { return mTypeId; } /// Higher number is higher priority (0 being the lowest) - virtual unsigned int getPriority() const {return 0;} + unsigned int getPriority() const { return mOptions.mPriority; } /// Check if package use movement with variable speed - virtual bool useVariableSpeed() const { return false;} + bool useVariableSpeed() const { return mOptions.mUseVariableSpeed; } virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} @@ -88,24 +99,24 @@ namespace MWMechanics virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); }; /// Return true if having this AiPackage makes the actor side with the target in fights (default false) - virtual bool sideWithTarget() const; + bool sideWithTarget() const { return mOptions.mSideWithTarget; } /// Return true if the actor should follow the target through teleport doors (default false) - virtual bool followTargetThroughDoors() const; + bool followTargetThroughDoors() const { return mOptions.mFollowTargetThroughDoors; } /// Can this Ai package be canceled? (default true) - virtual bool canCancel() const; + bool canCancel() const { return mOptions.mCanCancel; } /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? - virtual bool shouldCancelPreviousAi() const; + bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } /// Return true if this package should repeat. Currently only used for Wander packages. - virtual bool getRepeat() const; + bool getRepeat() const { return mOptions.mRepeat; } virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } - // Return true if any loaded actor with this AI package must be active. - virtual bool alwaysActive() const { return false; } + /// Return true if any loaded actor with this AI package must be active. + bool alwaysActive() const { return mOptions.mAlwaysActive; } /// Reset pathfinding state void reset(); @@ -138,6 +149,11 @@ namespace MWMechanics DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; + DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; + + const AiPackageTypeId mTypeId; + const Options mOptions; + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aipackagetypeid.hpp b/apps/openmw/mwmechanics/aipackagetypeid.hpp new file mode 100644 index 000000000..2b9c4fe9c --- /dev/null +++ b/apps/openmw/mwmechanics/aipackagetypeid.hpp @@ -0,0 +1,28 @@ +#ifndef GAME_MWMECHANICS_AIPACKAGETYPEID_H +#define GAME_MWMECHANICS_AIPACKAGETYPEID_H + +namespace MWMechanics +{ + ///Enumerates the various AITypes available + enum class AiPackageTypeId + { + None = -1, + Wander = 0, + Travel = 1, + Escort = 2, + Follow = 3, + Activate = 4, + + // These 5 are not really handled as Ai Packages in the MW engine + // For compatibility do *not* return these in the getCurrentAiPackage script function.. + Combat = 5, + Pursue = 6, + AvoidDoor = 7, + Face = 8, + Breathe = 9, + InternalTravel = 10, + Cast = 11 + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 52b2866bb..7aa2a9554 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -27,10 +27,6 @@ AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) mTargetActorId = pursue->mTargetActorId; } -AiPursue *MWMechanics::AiPursue::clone() const -{ - return new AiPursue(*this); -} bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { if(actor.getClass().getCreatureStats(actor).isDead()) @@ -70,11 +66,6 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte return false; } -int AiPursue::getTypeId() const -{ - return TypeIdPursue; -} - MWWorld::Ptr AiPursue::getTarget() const { return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index ea83a10e5..64465f530 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIPURSUE_H #define GAME_MWMECHANICS_AIPURSUE_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" namespace ESM { @@ -17,7 +17,7 @@ namespace MWMechanics /** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the path is completed). **/ - class AiPursue : public AiPackage + class AiPursue final : public TypedAiPackage { public: ///Constructor @@ -26,16 +26,21 @@ namespace MWMechanics AiPursue(const ESM::AiSequence::AiPursue* pursue); - virtual AiPursue *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); - virtual int getTypeId() const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - MWWorld::Ptr getTarget() const; + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Pursue; } - virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - virtual bool canCancel() const { return false; } - virtual bool shouldCancelPreviousAi() const { return false; } + MWWorld::Ptr getTarget() const final; + + void writeState (ESM::AiSequence::AiSequence& sequence) const final; }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5f3931fcf..f747b16f2 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -25,16 +25,15 @@ namespace MWMechanics void AiSequence::copy (const AiSequence& sequence) { - for (std::list::const_iterator iter (sequence.mPackages.begin()); - iter!=sequence.mPackages.end(); ++iter) - mPackages.push_back ((*iter)->clone()); + for (const auto& package : sequence.mPackages) + mPackages.push_back(package->clone()); // We need to keep an AiWander storage, if present - it has a state machine. // Not sure about another temporary storages sequence.mAiState.copy(mAiState); } -AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(-1) {} +AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(AiPackageTypeId::None) {} AiSequence::AiSequence (const AiSequence& sequence) { @@ -62,19 +61,19 @@ AiSequence::~AiSequence() clear(); } -int AiSequence::getTypeId() const +AiPackageTypeId AiSequence::getTypeId() const { if (mPackages.empty()) - return -1; + return AiPackageTypeId::None; return mPackages.front()->getTypeId(); } bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { - if (getTypeId() != AiPackage::TypeIdCombat) + if (getTypeId() != AiPackageTypeId::Combat) return false; - + targetActor = mPackages.front()->getTarget(); return !targetActor.isEmpty(); @@ -82,33 +81,32 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const bool AiSequence::getCombatTargets(std::vector &targetActors) const { - for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCombat) + if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Combat) targetActors.push_back((*it)->getTarget()); } return !targetActors.empty(); } -std::list::const_iterator AiSequence::begin() const +std::list>::const_iterator AiSequence::begin() const { return mPackages.begin(); } -std::list::const_iterator AiSequence::end() const +std::list>::const_iterator AiSequence::end() const { return mPackages.end(); } -void AiSequence::erase(std::list::const_iterator package) +void AiSequence::erase(std::list>::const_iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? - for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for(auto it = mPackages.begin(); it != mPackages.end(); ++it) { if (package == it) { - delete *it; mPackages.erase(it); return; } @@ -118,9 +116,9 @@ void AiSequence::erase(std::list::const_iterator package) bool AiSequence::isInCombat() const { - for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + if ((*it)->getTypeId() == AiPackageTypeId::Combat) return true; } return false; @@ -128,9 +126,9 @@ bool AiSequence::isInCombat() const bool AiSequence::isEngagedWithActor() const { - for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + if ((*it)->getTypeId() == AiPackageTypeId::Combat) { MWWorld::Ptr target2 = (*it)->getTarget(); if (!target2.isEmpty() && target2.getClass().isNpc()) @@ -140,9 +138,9 @@ bool AiSequence::isEngagedWithActor() const return false; } -bool AiSequence::hasPackage(int typeId) const +bool AiSequence::hasPackage(AiPackageTypeId typeId) const { - for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == typeId) return true; @@ -152,9 +150,9 @@ bool AiSequence::hasPackage(int typeId) const bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { - for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + if ((*it)->getTypeId() == AiPackageTypeId::Combat) { if ((*it)->getTarget() == actor) return true; @@ -165,11 +163,10 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const void AiSequence::stopCombat() { - for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) + for(auto it = mPackages.begin(); it != mPackages.end(); ) { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + if ((*it)->getTypeId() == AiPackageTypeId::Combat) { - delete *it; it = mPackages.erase(it); } else @@ -179,11 +176,10 @@ void AiSequence::stopCombat() void AiSequence::stopPursuit() { - for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) + for(auto it = mPackages.begin(); it != mPackages.end(); ) { - if ((*it)->getTypeId() == AiPackage::TypeIdPursue) + if ((*it)->getTypeId() == AiPackageTypeId::Pursue) { - delete *it; it = mPackages.erase(it); } else @@ -196,10 +192,13 @@ bool AiSequence::isPackageDone() const return mDone; } -bool isActualAiPackage(int packageTypeId) +namespace { - return (packageTypeId >= AiPackage::TypeIdWander && - packageTypeId <= AiPackage::TypeIdActivate); + bool isActualAiPackage(AiPackageTypeId packageTypeId) + { + return (packageTypeId >= AiPackageTypeId::Wander && + packageTypeId <= AiPackageTypeId::Activate); + } } void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) @@ -208,38 +207,38 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (mPackages.empty()) { - mLastAiPackage = -1; + mLastAiPackage = AiPackageTypeId::None; return; } - MWMechanics::AiPackage* package = mPackages.front(); + auto packageIt = mPackages.begin(); + MWMechanics::AiPackage* package = packageIt->get(); if (!package->alwaysActive() && outOfRange) return; - int packageTypeId = package->getTypeId(); + auto packageTypeId = package->getTypeId(); // workaround ai packages not being handled as in the vanilla engine if (isActualAiPackage(packageTypeId)) mLastAiPackage = packageTypeId; // if active package is combat one, choose nearest target - if (packageTypeId == AiPackage::TypeIdCombat) + if (packageTypeId == AiPackageTypeId::Combat) { - std::list::iterator itActualCombat; + auto itActualCombat = mPackages.end(); float nearestDist = std::numeric_limits::max(); osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); float bestRating = 0.f; - for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + for (auto it = mPackages.begin(); it != mPackages.end();) { - if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; + if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; MWWorld::Ptr target = (*it)->getTarget(); // target disappeared (e.g. summoned creatures) if (target.isEmpty()) { - delete *it; it = mPackages.erase(it); } else @@ -265,21 +264,23 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } } - if (!mPackages.empty()) - { - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) - { - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); - } + assert(!mPackages.empty()); - package = mPackages.front(); + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + assert(itActualCombat != mPackages.end()); + // move combat package with nearest target to the front + mPackages.splice(mPackages.begin(), mPackages, itActualCombat); } + + packageIt = mPackages.begin(); + package = packageIt->get(); + packageTypeId = package->getTypeId(); } try { - if (package->execute (actor, characterController, mAiState, duration)) + if (package->execute(actor, characterController, mAiState, duration)) { // Put repeating noncombat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) @@ -289,10 +290,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } // 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; + mPackages.erase(packageIt); if (isActualAiPackage(packageTypeId)) mDone = true; } @@ -310,9 +308,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac void AiSequence::clear() { - for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) - delete *iter; - mPackages.clear(); } @@ -328,37 +323,35 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. // Also there is no point to stack return packages. - int currentTypeId = getTypeId(); - int newTypeId = package.getTypeId(); - if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander - && !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel) - && (newTypeId <= MWMechanics::AiPackage::TypeIdCombat - || newTypeId == MWMechanics::AiPackage::TypeIdPursue - || newTypeId == MWMechanics::AiPackage::TypeIdCast)) + const auto currentTypeId = getTypeId(); + const auto newTypeId = package.getTypeId(); + if (currentTypeId <= MWMechanics::AiPackageTypeId::Wander + && !hasPackage(MWMechanics::AiPackageTypeId::InternalTravel) + && (newTypeId <= MWMechanics::AiPackageTypeId::Combat + || newTypeId == MWMechanics::AiPackageTypeId::Pursue + || newTypeId == MWMechanics::AiPackageTypeId::Cast)) { osg::Vec3f dest; - if (currentTypeId == MWMechanics::AiPackage::TypeIdWander) + if (currentTypeId == MWMechanics::AiPackageTypeId::Wander) { - AiPackage* activePackage = getActivePackage(); - dest = activePackage->getDestination(actor); + dest = getActivePackage().getDestination(actor); } else { dest = actor.getRefData().getPosition().asVec3(); } - MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true); + MWMechanics::AiInternalTravel travelPackage(dest.x(), dest.y(), dest.z()); stack(travelPackage, actor, false); } // remove previous packages if required if (cancelOther && package.shouldCancelPreviousAi()) { - for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + for (auto it = mPackages.begin(); it != mPackages.end();) { if((*it)->canCancel()) { - delete *it; it = mPackages.erase(it); } else @@ -368,23 +361,23 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo } // insert new package in correct place depending on priority - for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { // We should keep current AiCast package, if we try to add a new one. - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast && - package.getTypeId() == MWMechanics::AiPackage::TypeIdCast) + if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast && + package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) { continue; } if((*it)->getPriority() <= package.getPriority()) { - mPackages.insert(it,package.clone()); + mPackages.insert(it, package.clone()); return; } } - mPackages.push_back (package.clone()); + mPackages.push_back(package.clone()); // Make sure that temporary storage is empty if (cancelOther) @@ -400,12 +393,11 @@ bool MWMechanics::AiSequence::isEmpty() const return mPackages.empty(); } -AiPackage* MWMechanics::AiSequence::getActivePackage() +const AiPackage& MWMechanics::AiSequence::getActivePackage() { if(mPackages.empty()) throw std::runtime_error(std::string("No AI Package!")); - else - return mPackages.front(); + return *mPackages.front(); } void AiSequence::fill(const ESM::AIPackageList &list) @@ -416,7 +408,7 @@ void AiSequence::fill(const ESM::AIPackageList &list) for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { - MWMechanics::AiPackage* package; + std::unique_ptr package; if (it->mType == ESM::AI_Wander) { ESM::AIWander data = it->mWander; @@ -424,40 +416,38 @@ void AiSequence::fill(const ESM::AIPackageList &list) idles.reserve(8); for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); - package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); + package = std::make_unique(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); } else if (it->mType == ESM::AI_Escort) { ESM::AITarget data = it->mTarget; - package = new MWMechanics::AiEscort(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); } else if (it->mType == ESM::AI_Travel) { ESM::AITravel data = it->mTravel; - package = new MWMechanics::AiTravel(data.mX, data.mY, data.mZ); + package = std::make_unique(data.mX, data.mY, data.mZ); } else if (it->mType == ESM::AI_Activate) { ESM::AIActivate data = it->mActivate; - package = new MWMechanics::AiActivate(data.mName.toString()); + package = std::make_unique(data.mName.toString()); } else //if (it->mType == ESM::AI_Follow) { ESM::AITarget data = it->mTarget; - package = new MWMechanics::AiFollow(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); } - mPackages.push_back(package); + mPackages.push_back(std::move(package)); } } void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const { - for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) - { - (*iter)->writeState(sequence); - } + for (const auto& package : mPackages) + package->writeState(sequence); - sequence.mLastAiPackage = mLastAiPackage; + sequence.mLastAiPackage = static_cast(mLastAiPackage); } void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) @@ -470,7 +460,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) { - if (isActualAiPackage(it->mType)) + if (isActualAiPackage(static_cast(it->mType))) count++; } @@ -491,7 +481,11 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) } case ESM::AiSequence::Ai_Travel: { - package.reset(new AiTravel(static_cast(it->mPackage))); + const auto source = static_cast(it->mPackage); + if (source->mHidden) + package.reset(new AiInternalTravel(source)); + else + package.reset(new AiTravel(source)); break; } case ESM::AiSequence::Ai_Escort: @@ -526,18 +520,17 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!package.get()) continue; - mPackages.push_back(package.release()); + mPackages.push_back(std::move(package)); } - mLastAiPackage = sequence.mLastAiPackage; + mLastAiPackage = static_cast(sequence.mLastAiPackage); } void AiSequence::fastForward(const MWWorld::Ptr& actor) { if (!mPackages.empty()) { - MWMechanics::AiPackage* package = mPackages.front(); - package->fastForward(actor, mAiState); + mPackages.front()->fastForward(actor, mAiState); } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 7f07d5aae..645524d38 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -2,8 +2,10 @@ #define GAME_MWMECHANICS_AISEQUENCE_H #include +#include #include "aistate.hpp" +#include "aipackagetypeid.hpp" #include @@ -36,7 +38,7 @@ namespace MWMechanics class AiSequence { ///AiPackages to run though - std::list mPackages; + std::list> mPackages; ///Finished with top AIPackage, set for one frame bool mDone; @@ -48,7 +50,7 @@ namespace MWMechanics void copy (const AiSequence& sequence); /// The type of AI package that ran last - int mLastAiPackage; + AiPackageTypeId mLastAiPackage; AiState mAiState; public: @@ -64,20 +66,20 @@ namespace MWMechanics virtual ~AiSequence(); /// Iterator may be invalidated by any function calls other than begin() or end(). - std::list::const_iterator begin() const; - std::list::const_iterator end() const; + std::list>::const_iterator begin() const; + std::list>::const_iterator end() const; - void erase (std::list::const_iterator package); + void erase(std::list>::const_iterator package); /// Returns currently executing AiPackage type - /** \see enum AiPackage::TypeId **/ - int getTypeId() const; + /** \see enum class AiPackageTypeId **/ + AiPackageTypeId getTypeId() const; /// Get the typeid of the Ai package that ran last /** NOT the currently "active" Ai package that will be run in the next frame. This difference is important when an Ai package has just finished and been removed. - \see enum AiPackage::TypeId **/ - int getLastRunTypeId() const { return mLastAiPackage; } + \see enum class AiPackageTypeId **/ + AiPackageTypeId getLastRunTypeId() const { return mLastAiPackage; } /// Return true and assign target if combat package is currently active, return false otherwise bool getCombatTarget (MWWorld::Ptr &targetActor) const; @@ -92,7 +94,7 @@ namespace MWMechanics bool isEngagedWithActor () const; /// Does this AI sequence have the given package type? - bool hasPackage(int typeId) const; + bool hasPackage(AiPackageTypeId typeId) const; /// Are we in combat with this particular actor? bool isInCombat (const MWWorld::Ptr& actor) const; @@ -125,7 +127,7 @@ namespace MWMechanics /// Return the current active package. /** If there is no active package, it will throw an exception **/ - AiPackage* getActivePackage(); + const AiPackage& getActivePackage(); /// Fills the AiSequence with packages /** Typically used for loading from the ESM diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index dba70316b..b2a506ca6 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -26,31 +27,39 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z, bool hidden) - : mX(x),mY(y),mZ(z),mHidden(hidden) + AiTravel::AiTravel(float x, float y, float z, AiTravel*) + : mX(x), mY(y), mZ(z), mHidden(false) { } - AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden) + AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived) + : TypedAiPackage(derived), mX(x), mY(y), mZ(z), mHidden(true) { } - AiTravel *MWMechanics::AiTravel::clone() const + AiTravel::AiTravel(float x, float y, float z) + : AiTravel(x, y, z, this) + { + } + + AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) { - return new AiTravel(*this); + // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type + assert(!travel->mHidden); } bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - auto& stats = actor.getClass().getCreatureStats(actor); + MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); - if (stats.isTurningToPlayer() || stats.getGreetingState() == Greet_InProgress) + if (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress) return false; const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(mX, mY, mZ); + auto& stats = actor.getClass().getCreatureStats(actor); stats.setMovementFlag(CreatureStats::Flag_Run, false); stats.setDrawState(DrawState_Nothing); @@ -81,11 +90,6 @@ namespace MWMechanics return false; } - int AiTravel::getTypeId() const - { - return mHidden ? TypeIdInternalTravel : TypeIdTravel; - } - void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) @@ -110,5 +114,20 @@ namespace MWMechanics package.mPackage = travel.release(); sequence.mPackages.push_back(package); } + + AiInternalTravel::AiInternalTravel(float x, float y, float z) + : AiTravel(x, y, z, this) + { + } + + AiInternalTravel::AiInternalTravel(const ESM::AiSequence::AiTravel* travel) + : AiTravel(travel->mData.mX, travel->mData.mY, travel->mData.mZ, this) + { + } + + std::unique_ptr AiInternalTravel::clone() const + { + return std::make_unique(*this); + } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index e7895462f..0c19572d2 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AITRAVEL_H #define GAME_MWMECHANICS_AITRAVEL_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" namespace ESM { @@ -13,37 +13,56 @@ namespace AiSequence namespace MWMechanics { + struct AiInternalTravel; + /// \brief Causes the AI to travel to the specified point - class AiTravel : public AiPackage + class AiTravel : public TypedAiPackage { public: - /// Default constructor - AiTravel(float x, float y, float z, bool hidden = false); + AiTravel(float x, float y, float z, AiTravel* derived); + + AiTravel(float x, float y, float z, AiInternalTravel* derived); + + AiTravel(float x, float y, float z); + AiTravel(const ESM::AiSequence::AiTravel* travel); /// Simulates the passing of time - virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); + void fastForward(const MWWorld::Ptr& actor, AiState& state) final; + + void writeState(ESM::AiSequence::AiSequence &sequence) const final; - void writeState(ESM::AiSequence::AiSequence &sequence) const; + bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual AiTravel *clone() const; + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Travel; } - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mAlwaysActive = true; + return options; + } - virtual int getTypeId() const; + osg::Vec3f getDestination() const final { return osg::Vec3f(mX, mY, mZ); } - virtual bool useVariableSpeed() const { return true;} + private: + const float mX; + const float mY; + const float mZ; - virtual bool alwaysActive() const { return true; } + const bool mHidden; + }; - virtual osg::Vec3f getDestination() const { return osg::Vec3f(mX, mY, mZ); } + struct AiInternalTravel final : public AiTravel + { + AiInternalTravel(float x, float y, float z); - private: - float mX; - float mY; - float mZ; + explicit AiInternalTravel(const ESM::AiSequence::AiTravel* travel); + + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::InternalTravel; } - bool mHidden; + std::unique_ptr clone() const final; }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 597453409..11c50dc09 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,9 +1,12 @@ #include "aiwander.hpp" +#include + #include #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,7 +22,6 @@ #include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" -#include "coordinateconverter.hpp" #include "actorutil.hpp" namespace MWMechanics @@ -33,6 +35,8 @@ namespace MWMechanics // distance must be long enough that NPC will need to move to get there. static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2; + static const std::size_t MAX_IDLE_SIZE = 8; + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), @@ -94,30 +98,29 @@ namespace MWMechanics { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } - } - - AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): - mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), - mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false) - { - mIdle.resize(8, 0); - init(); - } - void AiWander::init() - { - // NOTE: mDistance and mDuration must be set already + std::vector getInitialIdle(const std::vector& idle) + { + std::vector result(MAX_IDLE_SIZE, 0); + std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin()); + return result; + } - if(mDistance < 0) - mDistance = 0; - if(mDuration < 0) - mDuration = 0; + std::vector getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE]) + { + return std::vector(std::begin(idle), std::end(idle)); + } } - AiPackage * MWMechanics::AiWander::clone() const + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): + TypedAiPackage(makeDefaultOptions().withRepeat(repeat)), + mDistance(std::max(0, distance)), + mDuration(std::max(0, duration)), + mRemainingDuration(duration), mTimeOfDay(timeOfDay), + mIdle(getInitialIdle(idle)), + mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), + mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false) { - return new AiWander(*this); } /* @@ -199,14 +202,14 @@ namespace MWMechanics { const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor)); + getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); } if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); } - GreetingState greetingState = cStats.getGreetingState(); + GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); if (greetingState == Greet_InProgress) { if (storage.mState == AiWanderStorage::Wander_Walking) @@ -240,7 +243,6 @@ namespace MWMechanics stopWalking(actor); // Reset package so it can be used again mRemainingDuration=mDuration; - init(); return true; } @@ -308,11 +310,6 @@ namespace MWMechanics return false; // AiWander package not yet completed } - bool AiWander::getRepeat() const - { - return mRepeat; - } - osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const { if (mHasDestination) @@ -340,6 +337,7 @@ namespace MWMechanics const auto halfExtents = world->getPathfindingHalfExtents(actor); const auto navigator = world->getNavigator(); const auto navigatorFlags = getNavigatorFlags(actor); + const auto areaCosts = getAreaCosts(actor); do { // Determine a random location within radius of original position @@ -368,7 +366,8 @@ namespace MWMechanics if (isWaterCreature || isFlyingCreature) mPathFinder.buildStraightPath(mDestination); else - mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags); + mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags, + areaCosts); if (mPathFinder.isPathConstructed()) { @@ -442,7 +441,7 @@ namespace MWMechanics } // Check if idle animation finished - GreetingState greetingState = actor.getClass().getCreatureStats(actor).getGreetingState(); + GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { if (mPathFinder.isPathConstructed()) @@ -499,7 +498,8 @@ namespace MWMechanics if (mUsePathgrid) { const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor)); + mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor), + getAreaCosts(actor)); } if (mObstacleCheck.isEvading()) @@ -569,7 +569,7 @@ namespace MWMechanics void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) { - CoordinateConverter(cell).toWorld(point); + Misc::CoordinateConverter(cell).toWorld(point); } void AiWander::trimAllowedNodes(std::vector& nodes, @@ -598,11 +598,6 @@ namespace MWMechanics } } - int AiWander::getTypeId() const - { - return TypeIdWander; - } - void AiWander::stopWalking(const MWWorld::Ptr& actor) { mPathFinder.clearPath(); @@ -775,7 +770,7 @@ namespace MWMechanics { // get NPC's position in local (i.e. cell) coordinates osg::Vec3f npcPos(mInitialActorPosition); - CoordinateConverter(cell).toLocal(npcPos); + Misc::CoordinateConverter(cell).toLocal(npcPos); // Find closest pathgrid point int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos); @@ -872,7 +867,7 @@ namespace MWMechanics assert (mIdle.size() == 8); for (int i=0; i<8; ++i) wander->mData.mIdle[i] = mIdle[i]; - wander->mData.mShouldRepeat = mRepeat; + wander->mData.mShouldRepeat = mOptions.mRepeat; wander->mStoredInitialActorPosition = mStoredInitialActorPosition; if (mStoredInitialActorPosition) wander->mInitialActorPosition = mInitialActorPosition; @@ -884,11 +879,12 @@ namespace MWMechanics } AiWander::AiWander (const ESM::AiSequence::AiWander* wander) - : mDistance(wander->mData.mDistance) - , mDuration(wander->mData.mDuration) + : TypedAiPackage(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0)) + , mDistance(std::max(static_cast(0), wander->mData.mDistance)) + , mDuration(std::max(static_cast(0), wander->mData.mDuration)) , mRemainingDuration(wander->mDurationData.mRemainingDuration) , mTimeOfDay(wander->mData.mTimeOfDay) - , mRepeat(wander->mData.mShouldRepeat != 0) + , mIdle(getInitialIdle(wander->mData.mIdle)) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) , mHasDestination(false) , mDestination(osg::Vec3f(0, 0, 0)) @@ -896,11 +892,7 @@ namespace MWMechanics { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; - for (int i=0; i<8; ++i) - mIdle.push_back(wander->mData.mIdle[i]); if (mRemainingDuration <= 0 || mRemainingDuration >= 24) mRemainingDuration = mDuration; - - init(); } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6e69b6c79..4138c3dea 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_AIWANDER_H #define GAME_MWMECHANICS_AIWANDER_H -#include "aipackage.hpp" +#include "typedaipackage.hpp" #include @@ -78,7 +78,7 @@ namespace MWMechanics }; /// \brief Causes the Actor to wander within a specified range - class AiWander : public AiPackage + class AiWander final : public TypedAiPackage { public: /// Constructor @@ -91,23 +91,25 @@ namespace MWMechanics AiWander (const ESM::AiSequence::AiWander* wander); - virtual AiPackage *clone() const; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) final; - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Wander; } - virtual int getTypeId() const; - - virtual bool useVariableSpeed() const { return true;} - - virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mRepeat = false; + return options; + } - virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); + void writeState(ESM::AiSequence::AiSequence &sequence) const final; - bool getRepeat() const; + void fastForward(const MWWorld::Ptr& actor, AiState& state) final; - osg::Vec3f getDestination(const MWWorld::Ptr& actor) const; + osg::Vec3f getDestination(const MWWorld::Ptr& actor) const final; - virtual osg::Vec3f getDestination() const + osg::Vec3f getDestination() const final { if (!mHasDestination) return osg::Vec3f(0, 0, 0); @@ -116,8 +118,6 @@ namespace MWMechanics } private: - // NOTE: mDistance and mDuration must be set already - void init(); void stopWalking(const MWWorld::Ptr& actor); /// Have the given actor play an idle animation @@ -138,12 +138,11 @@ namespace MWMechanics bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); - int mDistance; // how far the actor can wander from the spawn point - int mDuration; + const int mDistance; // how far the actor can wander from the spawn point + const int mDuration; float mRemainingDuration; - int mTimeOfDay; - std::vector mIdle; - bool mRepeat; + const int mTimeOfDay; + const std::vector mIdle; bool mStoredInitialActorPosition; osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell @@ -178,7 +177,7 @@ namespace MWMechanics static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); - }; + }; } #endif diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index b490db436..116937fcd 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -468,7 +468,7 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) { - int alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy); + float alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 6d3090918..9cee1aa31 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -262,6 +262,8 @@ namespace MWMechanics int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) duration = effect.mDuration; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) + duration = std::max(1, duration); static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() .get().find("fEffectCostMult")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3ae14a5ce..ddad011c9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -859,6 +859,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mAttackingOrSpell(false) , mCastingManualSpell(false) , mTimeUntilWake(0.f) + , mIsMovingBackward(false) { if(!mAnimation) return; @@ -948,7 +949,7 @@ void split(const std::string &s, char delim, std::vector &elems) { } } -void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) +void CharacterController::handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, const NifOsg::TextKeyMap& map) { const std::string &evt = key->second; @@ -1266,10 +1267,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle) } } - // Use blending only with 3d-person movement animations for bipedal actors - bool firstPersonPlayer = (mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()); + // For biped actors, blend weapon animations with lower body animations with higher priority MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - if (!firstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) + if (mPtr.getClass().isBipedal(mPtr)) priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; @@ -1952,64 +1952,79 @@ void CharacterController::update(float duration, bool animationOnly) bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; CreatureStats &stats = cls.getCreatureStats(mPtr); + Movement& movementSettings = cls.getMovementSettings(mPtr); //Force Jump Logic - bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5); if(!inwater && !flying && solid) { //Force Jump if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) - { - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } + movementSettings.mPosition[2] = onground ? 1 : 0; //Force Move Jump, only jump if they're otherwise moving if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) - { - - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } + movementSettings.mPosition[2] = onground ? 1 : 0; } - osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); - vec.normalize(); - - if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = osg::Vec3f(0.f, 0.f, 0.f); osg::Vec3f rot = cls.getRotationVector(mPtr); + osg::Vec3f vec(movementSettings.asVec3()); + vec.normalize(); - speed = cls.getSpeed(mPtr); - float analogueMult = 1.f; - if(isPlayer) + float analogueMult = 1.0f; + if (isPlayer) { + // TODO: Move this code to mwinput. // Joystick analogue movement. - float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]); - float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]); - analogueMult = ((xAxis > yAxis) ? xAxis : yAxis); - - // If Strafing, our max speed is slower so multiply by X axis instead. - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) - analogueMult = xAxis; + float xAxis = std::abs(movementSettings.mPosition[0]); + float yAxis = std::abs(movementSettings.mPosition[1]); + analogueMult = std::max(xAxis, yAxis); // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. if(!isrunning && !sneak && !flying && analogueMult <= 0.5f) analogueMult *= 2.f; + + movementSettings.mSpeedFactor = analogueMult; } - speed *= analogueMult; + float effectiveRotation = rot.z(); + static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); + if (turnToMovementDirection && !(isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson())) + { + float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); + movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater) + && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f); + if (movementSettings.mIsStrafing) + targetMovementAngle = 0; + float delta = targetMovementAngle - stats.getSideMovementAngle(); + float cosDelta = cosf(delta); + + if ((vec.y() < 0) == mIsMovingBackward) + movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn + if (std::abs(delta) < osg::DegreesToRadians(20.0f)) + mIsMovingBackward = vec.y() < 0; + + float maxDelta = osg::PI * duration * (2.5f - cosDelta); + delta = osg::clampBetween(delta, -maxDelta, maxDelta); + stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); + effectiveRotation += delta; + } + else + movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; + + mAnimation->setLegsYawRadians(stats.getSideMovementAngle()); + if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater) + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2); + else + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); + + speed = cls.getSpeed(mPtr); vec.x() *= speed; vec.y() *= speed; + if(mHitState != CharState_None && mJumpState == JumpState_None) + vec = osg::Vec3f(); + CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; JumpingState jumpstate = JumpState_None; @@ -2141,7 +2156,7 @@ void CharacterController::update(float duration, bool animationOnly) cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); } - const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); + const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); if (healthLost > (acrobaticsSkill * fatigueTerm)) { if (!godmode) @@ -2171,7 +2186,7 @@ void CharacterController::update(float duration, bool animationOnly) inJump = false; - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) + if (movementSettings.mIsStrafing) { if(vec.x() > 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) @@ -2182,18 +2197,18 @@ void CharacterController::update(float duration, bool animationOnly) : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); } - else if(vec.y() != 0.0f) + else if (vec.length2() > 0.0f) { - if(vec.y() > 0.0f) + if (vec.y() >= 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y() < 0.0f) + else movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z() != 0.0f) + else if (effectiveRotation != 0.0f) { // Do not play turning animation for player if rotation speed is very slow. // Actual threshold should take framerate in account. @@ -2206,9 +2221,9 @@ void CharacterController::update(float duration, bool animationOnly) bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) { - if(rot.z() > rotationThreshold) + if(effectiveRotation > rotationThreshold) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - else if(rot.z() < -rotationThreshold) + else if(effectiveRotation < -rotationThreshold) movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; } } @@ -2330,9 +2345,9 @@ void CharacterController::update(float duration, bool animationOnly) world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; if (movement.z() == 0.f) - cls.getMovementSettings(mPtr).mPosition[2] = 0; + movementSettings.mPosition[2] = 0; // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. @@ -2368,20 +2383,17 @@ void CharacterController::update(float duration, bool animationOnly) if(speed > 0.f) { float l = moved.length(); - - if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || - (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) - moved.x() = movement.x(); - if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || - (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) - moved.y() = movement.y(); - if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || - (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) - moved.z() = movement.z(); - // but keep the original speed - float newLength = moved.length(); - if (newLength > 0) - moved *= (l / newLength); + if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 || + std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 || + std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) + { + moved = movement; + // For some creatures getSpeed doesn't work, so we adjust speed to the animation. + // TODO: Fix Creature::getSpeed. + float newLength = moved.length(); + if (newLength > 0 && !cls.isNpc()) + moved *= (l / newLength); + } } if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 614beca1c..6092ca724 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -195,6 +195,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener float mTimeUntilWake; + bool mIsMovingBackward; + void setAttackTypeBasedOnMovement(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); @@ -238,8 +240,7 @@ public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); - virtual void handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, - const std::multimap& map); + virtual void handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, const NifOsg::TextKeyMap& map); // Be careful when to call this, see comment in Actors void updateContinuousVfx(); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index b4a5508cb..7a12bf7b8 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -101,7 +101,7 @@ namespace MWMechanics blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat(); blockerTerm *= blockerStats.getFatigueTerm(); - int attackerSkill = 0; + float attackerSkill = 0; if (weapon.isEmpty()) attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand); else diff --git a/apps/openmw/mwmechanics/coordinateconverter.cpp b/apps/openmw/mwmechanics/coordinateconverter.cpp deleted file mode 100644 index 04155ea49..000000000 --- a/apps/openmw/mwmechanics/coordinateconverter.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "coordinateconverter.hpp" - -#include -#include - -namespace MWMechanics -{ - CoordinateConverter::CoordinateConverter(const ESM::Cell* cell) - : mCellX(0), mCellY(0) - { - if (cell->isExterior()) - { - mCellX = cell->mData.mX * ESM::Land::REAL_SIZE; - mCellY = cell->mData.mY * ESM::Land::REAL_SIZE; - } - } - - void CoordinateConverter::toWorld(ESM::Pathgrid::Point& point) - { - point.mX += mCellX; - point.mY += mCellY; - } - - void CoordinateConverter::toWorld(osg::Vec3f& point) - { - point.x() += static_cast(mCellX); - point.y() += static_cast(mCellY); - } - - void CoordinateConverter::toLocal(osg::Vec3f& point) - { - point.x() -= static_cast(mCellX); - point.y() -= static_cast(mCellY); - } - - osg::Vec3f CoordinateConverter::toLocalVec3(const osg::Vec3f& point) - { - return osg::Vec3f( - point.x() - static_cast(mCellX), - point.y() - static_cast(mCellY), - point.z() - ); - } -} diff --git a/apps/openmw/mwmechanics/coordinateconverter.hpp b/apps/openmw/mwmechanics/coordinateconverter.hpp deleted file mode 100644 index f7dda33cb..000000000 --- a/apps/openmw/mwmechanics/coordinateconverter.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef GAME_MWMECHANICS_COORDINATECONVERTER_H -#define GAME_MWMECHANICS_COORDINATECONVERTER_H - -#include -#include - -namespace ESM -{ - struct Cell; -} - -namespace MWMechanics -{ - /// \brief convert coordinates between world and local cell - class CoordinateConverter - { - public: - CoordinateConverter(const ESM::Cell* cell); - - /// in-place conversion from local to world - void toWorld(ESM::Pathgrid::Point& point); - - /// in-place conversion from local to world - void toWorld(osg::Vec3f& point); - - /// in-place conversion from world to local - void toLocal(osg::Vec3f& point); - - osg::Vec3f toLocalVec3(const osg::Vec3f& point); - - private: - int mCellX; - int mCellY; - }; -} - -#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 1c377540a..79b8e23de 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -23,53 +23,12 @@ namespace MWMechanics mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), - mDeathAnimation(-1), mTimeOfDeath(), mGreetingState(Greet_None), - mGreetingTimer(0), mTargetAngleRadians(0), mIsTurningToPlayer(false), mLevel (0) + mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; } - int MWMechanics::CreatureStats::getGreetingTimer() const - { - return mGreetingTimer; - } - - void MWMechanics::CreatureStats::setGreetingTimer(int timer) - { - mGreetingTimer = timer; - } - - float MWMechanics::CreatureStats::getAngleToPlayer() const - { - return mTargetAngleRadians; - } - - void MWMechanics::CreatureStats::setAngleToPlayer(float angle) - { - mTargetAngleRadians = angle; - } - - GreetingState MWMechanics::CreatureStats::getGreetingState() const - { - return mGreetingState; - } - - void MWMechanics::CreatureStats::setGreetingState(GreetingState state) - { - mGreetingState = state; - } - - bool MWMechanics::CreatureStats::isTurningToPlayer() const - { - return mIsTurningToPlayer; - } - - void MWMechanics::CreatureStats::setTurningToPlayer(bool turning) - { - mIsTurningToPlayer = turning; - } - const AiSequence& CreatureStats::getAiSequence() const { return mAiSequence; @@ -167,7 +126,7 @@ namespace MWMechanics return mMagicEffects; } - void CreatureStats::setAttribute(int index, int base) + void CreatureStats::setAttribute(int index, float base) { AttributeValue current = getAttribute(index); current.setBase(base); @@ -193,10 +152,10 @@ namespace MWMechanics index == ESM::Attribute::Agility || index == ESM::Attribute::Endurance) { - int strength = getAttribute(ESM::Attribute::Strength).getModified(); - int willpower = getAttribute(ESM::Attribute::Willpower).getModified(); - int agility = getAttribute(ESM::Attribute::Agility).getModified(); - int endurance = getAttribute(ESM::Attribute::Endurance).getModified(); + float strength = getAttribute(ESM::Attribute::Strength).getModified(); + float willpower = getAttribute(ESM::Attribute::Willpower).getModified(); + float agility = getAttribute(ESM::Attribute::Agility).getModified(); + float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; @@ -592,6 +551,14 @@ namespace MWMechanics state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); + + for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + for (int i=0; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; + + state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); + } } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -630,7 +597,7 @@ namespace MWMechanics mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); //mHitAttemptActorId = state.mHitAttemptActorId; - mSpells.readState(state.mSpells); + mSpells.readState(state.mSpells, this); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); mMagicEffects.readState(state.mMagicEffects); @@ -641,6 +608,15 @@ namespace MWMechanics if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); + + mCorprusSpells.clear(); + for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + { + for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; + + mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); + } } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -716,4 +692,23 @@ namespace MWMechanics { return mSummonGraveyard; } + + std::map &CreatureStats::getCorprusSpells() + { + return mCorprusSpells; + } + + void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats) + { + mCorprusSpells[sourceId] = stats; + } + + void CreatureStats::removeCorprusSpell(const std::string& sourceId) + { + auto corprusIt = mCorprusSpells.find(sourceId); + if (corprusIt != mCorprusSpells.end()) + { + mCorprusSpells.erase(corprusIt); + } + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 7c4a83db1..5e91a1b5a 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -12,6 +12,8 @@ #include "aisequence.hpp" #include "drawstate.hpp" +#include + namespace ESM { struct CreatureStats; @@ -19,11 +21,12 @@ namespace ESM namespace MWMechanics { - enum GreetingState + struct CorprusStats { - Greet_None, - Greet_InProgress, - Greet_Done + static const int sWorseningPeriod = 24; + + int mWorsenings[ESM::Attribute::Length]; + MWWorld::TimeStamp mNextWorsening; }; /// \brief Common creature stats @@ -33,7 +36,7 @@ namespace MWMechanics { static int sActorId; DrawState_ mDrawState; - AttributeValue mAttributes[8]; + AttributeValue mAttributes[ESM::Attribute::Length]; DynamicStat mDynamic[3]; // health, magicka, fatigue Spells mSpells; ActiveSpells mActiveSpells; @@ -77,10 +80,8 @@ namespace MWMechanics MWWorld::TimeStamp mTimeOfDeath; - GreetingState mGreetingState; - int mGreetingTimer; - float mTargetAngleRadians; - bool mIsTurningToPlayer; + // The difference between view direction and lower body direction. + float mSideMovementAngle; public: typedef std::pair SummonKey; // @@ -91,24 +92,14 @@ namespace MWMechanics // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; + std::map mCorprusSpells; + protected: int mLevel; public: CreatureStats(); - int getGreetingTimer() const; - void setGreetingTimer(int timer); - - float getAngleToPlayer() const; - void setAngleToPlayer(float angle); - - GreetingState getGreetingState() const; - void setGreetingState(GreetingState state); - - bool isTurningToPlayer() const; - void setTurningToPlayer(bool turning); - DrawState_ getDrawState() const; void setDrawState(DrawState_ state); @@ -150,7 +141,7 @@ namespace MWMechanics void setAttribute(int index, const AttributeValue &value); // Shortcut to set only the base - void setAttribute(int index, int base); + void setAttribute(int index, float base); void setHealth(const DynamicStat &value); @@ -304,6 +295,15 @@ namespace MWMechanics /// assigned this function will return false). static void cleanup(); + + std::map & getCorprusSpells(); + + void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); + + void removeCorprusSpell(const std::string& sourceId); + + float getSideMovementAngle() const { return mSideMovementAngle; } + void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 25b33c486..d7da4ebb7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,10 +1,14 @@ #include "mechanicsmanagerimp.hpp" +#include + #include #include #include +#include + #include #include "../mwworld/esmstore.hpp" @@ -287,6 +291,24 @@ namespace MWMechanics mWatched = ptr; } + void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) + { + auto& stats = actor.getClass().getCreatureStats (actor); + auto& corprusSpells = stats.getCorprusSpells(); + + auto corprusIt = corprusSpells.find(sourceId); + + if (corprusIt != corprusSpells.end()) + { + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + MWMechanics::AttributeValue attr = stats.getAttribute(i); + attr.restore(corprusIt->second.mWorsenings[i]); + actor.getClass().getCreatureStats(actor).setAttribute(i, attr); + } + } + } + void MechanicsManager::update(float duration, bool paused) { if(!mWatched.isEmpty()) @@ -657,7 +679,9 @@ namespace MWMechanics int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) { - if (ptr.getTypeName() == typeid(ESM::Creature).name()) + // Make sure zero base price items/services can't be bought/sold for 1 gold + // and return the intended base price for creature merchants + if (basePrice == 0 || ptr.getTypeName() == typeid(ESM::Creature).name()) return basePrice; const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); @@ -668,10 +692,10 @@ namespace MWMechanics // I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered here, // otherwise one would get different prices when exiting and re-entering the dialogue window... int clampedDisposition = getDerivedDisposition(ptr); - float a = static_cast(std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100)); + float a = std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100.f); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d = static_cast(std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100)); + float d = std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100.f); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); @@ -762,8 +786,11 @@ namespace MWMechanics { if (std::abs(c) < iPerMinChange) { - x = 0; - y = -iPerMinChange; + // Deviating from Morrowind here: it doesn't increase disposition on marginal wins, + // which seems to be a bug (MCP fixes it too). + // Original logic: x = 0, y = -iPerMinChange + x = -iPerMinChange; + y = x; // This goes unused. } else { @@ -880,6 +907,12 @@ namespace MWMechanics bool MechanicsManager::toggleAI() { mAI = !mAI; + + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->getNavigator()->setUpdatesEnabled(mAI); + if (mAI) + world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); + return mAI; } @@ -1197,7 +1230,7 @@ namespace MWMechanics if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { - if (victim.isEmpty() || (victim.getClass().isActor() && !victim.getClass().getCreatureStats(victim).isDead())) + if (victim.isEmpty() || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead())) mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; } if (alarm) @@ -1257,7 +1290,7 @@ namespace MWMechanics { bool reported = false; if (victim.getClass().isClass(victim, "guard") - && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) reported = reportCrime(player, victim, type, std::string(), arg); if (!reported) @@ -1280,7 +1313,7 @@ namespace MWMechanics return false; // Player's followers should not attack player, or try to arrest him - if (actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackage::TypeIdFollow)) + if (actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Follow)) { if (playerFollowers.find(actor) != playerFollowers.end()) return false; @@ -1397,7 +1430,7 @@ namespace MWMechanics // once the bounty has been paid. actor.getClass().getNpcStats(actor).setCrimeId(id); - if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) { actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); } @@ -1475,7 +1508,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) startCombat(victim, player); // Set the crime ID, which we will use to calm down participants @@ -1521,7 +1554,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) { // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player, // he will attack the player only if we will force him (e.g. via StartCombat console command) @@ -1546,7 +1579,7 @@ namespace MWMechanics const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue); + && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue); } void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) @@ -1601,8 +1634,8 @@ namespace MWMechanics static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat(); static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat(); float sneak = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak)); - int agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); - int luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float bootWeight = 0; if (ptr.getClass().isNpc() && MWBase::Environment::get().getWorld()->isOnGround(ptr)) { @@ -1625,10 +1658,10 @@ namespace MWMechanics float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; CreatureStats& observerStats = observer.getClass().getCreatureStats(observer); - int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); - int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); + float obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude(); - int obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); + float obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); float obsTerm = obsSneak + 0.2f * obsAgility + 0.1f * obsLuck - obsBlind; @@ -1682,7 +1715,7 @@ namespace MWMechanics if (iter->first.getClass().isClass(iter->first, "Guard")) { MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); - if (aiSeq.getTypeId() == MWMechanics::AiPackage::TypeIdPursue) + if (aiSeq.getTypeId() == MWMechanics::AiPackageTypeId::Pursue) { aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); @@ -1944,4 +1977,29 @@ namespace MWMechanics mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId); } + void MechanicsManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "Mechanics Actors", mActors.size()); + stats.setAttribute(frameNumber, "Mechanics Objects", mObjects.size()); + } + + int MechanicsManager::getGreetingTimer(const MWWorld::Ptr &ptr) const + { + return mActors.getGreetingTimer(ptr); + } + + float MechanicsManager::getAngleToPlayer(const MWWorld::Ptr &ptr) const + { + return mActors.getAngleToPlayer(ptr); + } + + GreetingState MechanicsManager::getGreetingState(const MWWorld::Ptr &ptr) const + { + return mActors.getGreetingState(ptr); + } + + bool MechanicsManager::isTurningToPlayer(const MWWorld::Ptr &ptr) const + { + return mActors.isTurningToPlayer(ptr); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 640bd3bdd..86bc4c720 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -240,6 +240,15 @@ namespace MWMechanics virtual bool isRunning(const MWWorld::Ptr& ptr) override; virtual bool isSneaking(const MWWorld::Ptr& ptr) override; + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; + + virtual int getGreetingTimer(const MWWorld::Ptr& ptr) const override; + virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const override; + virtual GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; + virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; + + virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override; + private: bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index cb9087359..86b970e60 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -11,12 +11,14 @@ namespace MWMechanics float mPosition[3]; float mRotation[3]; float mSpeedFactor; + bool mIsStrafing; Movement() { mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; mSpeedFactor = 1.f; + mIsStrafing = false; } osg::Vec3f asVec3() diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index ee48ea7d5..5d19368bf 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -226,9 +226,9 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress, bool readBook) { - int base = getSkill (skillIndex).getBase(); + float base = getSkill (skillIndex).getBase(); - if (base >= 100) + if (base >= 100.f) return; base += 1; @@ -265,7 +265,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWindowManager()->playSound("skillraise"); std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""); - message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), base); + message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), static_cast(base)); if (readBook) message = "#{sBookSkillMessage}\n" + message; @@ -299,7 +299,7 @@ void MWMechanics::NpcStats::levelUp() for (int i=0; i=rankData.mSkill2; + iter++; + if (*iter& out); + + std::size_t size() const + { + return mObjects.size(); + } }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index b072f55f8..4d4b5be51 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -17,7 +18,6 @@ #include "../mwworld/class.hpp" #include "pathgrid.hpp" -#include "coordinateconverter.hpp" #include "actorutil.hpp" namespace @@ -158,13 +158,10 @@ namespace MWMechanics // Maybe there is no pathgrid for this cell. Just go to destination and let // physics take care of any blockages. if(!pathgrid || pathgrid->mPoints.empty()) - { - *out++ = endPoint; return; - } // NOTE: getClosestPoint expects local coordinates - CoordinateConverter converter(mCell->getCell()); + Misc::CoordinateConverter converter(mCell->getCell()); // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing @@ -179,6 +176,9 @@ namespace MWMechanics endPointInLocalCoords, startNode); + if (!endNode.second) + return; + // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); @@ -249,8 +249,7 @@ namespace MWMechanics // unreachable pathgrid point. // // The AI routines will have to deal with such situations. - if(endNode.second) - *out++ = endPoint; + *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const @@ -310,12 +309,13 @@ namespace MWMechanics } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, - const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts) { mPath.clear(); // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path - if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath))) + if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath))) mPath.push_back(endPoint); mConstructed = true; @@ -323,28 +323,37 @@ namespace MWMechanics void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags) + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { mPath.clear(); mCell = cell; + bool hasNavMesh = false; + if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) - buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + hasNavMesh = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); + + if (hasNavMesh && mPath.empty()) + buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, + flags | DetourNavigator::Flag_usePathgrid, areaCosts, std::back_inserter(mPath)); if (mPath.empty()) buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); + if (!hasNavMesh && mPath.empty()) + mPath.push_back(endPoint); + mConstructed = true; } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - std::back_insert_iterator> out) + const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out) { const auto world = MWBase::Environment::get().getWorld(); const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); + const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out); if (status == DetourNavigator::Status::NavMeshNotFound) return false; @@ -361,7 +370,7 @@ namespace MWMechanics } void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags) + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { if (mPath.empty()) return; @@ -375,7 +384,7 @@ namespace MWMechanics const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); std::deque prePath; auto prePathInserter = std::back_inserter(prePath); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, + const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, areaCosts, prePathInserter); if (status == DetourNavigator::Status::NavMeshNotFound) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index cb33471ca..5af822fee 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -90,14 +91,15 @@ namespace MWMechanics const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, - const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts); void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags); + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags); + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); @@ -203,7 +205,7 @@ namespace MWMechanics bool buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - std::back_insert_iterator> out); + const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out); }; } diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 7bcdc8926..ee1de3b5a 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -52,7 +52,6 @@ namespace MWMechanics PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) : mCell(nullptr) , mPathgrid(nullptr) - , mIsExterior(0) , mGraph(0) , mIsGraphConstructed(false) , mSCCId(0) @@ -106,7 +105,6 @@ namespace MWMechanics return true; mCell = cell->getCell(); - mIsExterior = cell->getCell()->isExterior(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell()); if(!mPathgrid) return false; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 6b67bb507..050504617 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -44,7 +44,6 @@ namespace MWMechanics const ESM::Cell *mCell; const ESM::Pathgrid *mPathgrid; - bool mIsExterior; struct ConnectedPoint // edge { diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index ed6469106..05e8a0393 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -22,8 +22,8 @@ namespace MWMechanics float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add) { NpcStats& stats = ptr.getClass().getNpcStats(ptr); - float agility = static_cast(stats.getAttribute(ESM::Attribute::Agility).getModified()); - float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); + float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float sneak = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak)); return (add + 0.2f * agility + 0.1f * luck + sneak) * stats.getFatigueTerm(); } diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index faa0e3b09..389d00d85 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -32,9 +32,9 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); float fatigueTerm = stats.getFatigueTerm(); - int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); - int pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - int armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); + float pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); + float pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fRepairAmountMult")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index ab286cbee..001375feb 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -19,8 +19,8 @@ namespace MWMechanics : mActor(actor) { CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - mAgility = static_cast(creatureStats.getAttribute(ESM::Attribute::Agility).getModified()); - mLuck = static_cast(creatureStats.getAttribute(ESM::Attribute::Luck).getModified()); + mAgility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); + mLuck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); mSecuritySkill = static_cast(actor.getClass().getSkill(actor, ESM::Skill::Security)); mFatigueTerm = creatureStats.getFatigueTerm(); } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 044a4338e..9f7108239 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -60,8 +60,13 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { - if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) - return; + if (!target.isEmpty() && target.getClass().isActor()) + { + // Early-out for characters that have departed. + const auto& stats = target.getClass().getCreatureStats(target); + if (stats.isDead() && stats.isDeathAnimationFinished()) + return; + } // If none of the effects need to apply, we can early-out bool found = false; @@ -183,6 +188,7 @@ namespace MWMechanics effect.mEffectId = effectIt->mEffectID; effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; effect.mMagnitude = magnitude; + effect.mTimeLeft = 0.f; // Avoid applying absorb effects if the caster is the target // We still need the spell to be added @@ -200,17 +206,22 @@ namespace MWMechanics } bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != caster && effectAffectsHealth) + if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth) { - // If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar. + // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. MWBase::Environment::get().getWindowManager()->setEnemy(target); } bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - if (hasDuration && effectIt->mDuration == 0) + effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; + + bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); + + if (effect.mDuration == 0) { // We still should add effect to list to allow GetSpellEffects to detect this spell - effect.mDuration = 0.f; appliedLastingEffects.push_back(effect); // duration 0 means apply full magnitude instantly @@ -223,7 +234,7 @@ namespace MWMechanics } else { - effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; + effect.mTimeLeft = effect.mDuration; targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 9428beafc..81658193d 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -394,8 +394,9 @@ namespace MWMechanics priority = 10; const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const DynamicStat& current = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + // NB: this currently assumes the hardcoded magic effect flags are used const float magnitude = (effect.mMagnMin + effect.mMagnMax)/2.f; - const float toHeal = magnitude * effect.mDuration; + const float toHeal = magnitude * std::max(1, effect.mDuration); // Effect doesn't heal more than we need, *or* we are below 1/2 health if (current.getModified() - current.getCurrent() > toHeal || current.getCurrent() < current.getModified()*0.5) diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index a187600fb..1edf14091 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -40,8 +40,8 @@ namespace MWMechanics float resistance = getEffectResistanceAttribute(effectId, magicEffects); - int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); - float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); + float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 0eaa531e2..ae7454f19 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -7,9 +7,13 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "actorutil.hpp" +#include "creaturestats.hpp" #include "magiceffects.hpp" +#include "stat.hpp" namespace MWMechanics { @@ -63,12 +67,6 @@ namespace MWMechanics } } } - - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - mEffects += it->second; - mSourcedEffects[it->first] += it->second; - } } bool Spells::hasSpell(const std::string &spell) const @@ -101,15 +99,6 @@ namespace MWMechanics } } - if (hasCorprusEffect(spell)) - { - CorprusStats corprus; - corprus.mWorsenings = 0; - corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - - mCorprusSpells[spell] = corprus; - } - SpellParams params; params.mEffectRands = random; mSpells.insert (std::make_pair (spell, params)); @@ -127,27 +116,6 @@ namespace MWMechanics const ESM::Spell* spell = getSpell(spellId); TContainer::iterator iter = mSpells.find (spell); - std::map::iterator corprusIt = mCorprusSpells.find(spell); - - // if it's corprus, remove negative and keep positive effects - if (corprusIt != mCorprusSpells.end()) - { - worsenCorprus(spell); - if (mPermanentSpellEffects.find(spell) != mPermanentSpellEffects.end()) - { - MagicEffects & effects = mPermanentSpellEffects[spell]; - for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end();) - { - const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->first.mId); - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - effects.remove((effectIt++)->first); - else - ++effectIt; - } - } - mCorprusSpells.erase(corprusIt); - } - if (iter!=mSpells.end()) { mSpells.erase (iter); @@ -320,31 +288,6 @@ namespace MWMechanics } } - void Spells::worsenCorprus(const ESM::Spell* spell) - { - mCorprusSpells[spell].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spell].mWorsenings++; - - // update worsened effects - mPermanentSpellEffects[spell] = MagicEffects(); - int i=0; - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i) - { - const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); - if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) - { - float random = 1.f; - if (mSpells[spell].mEffectRands.find(i) != mSpells[spell].mEffectRands.end()) - random = mSpells[spell].mEffectRands.at(i); - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings); - mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); - mSpellsChanged = true; - } - } - } - bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) @@ -357,11 +300,6 @@ namespace MWMechanics return false; } - const std::map &Spells::getCorprusSpells() const - { - return mCorprusSpells; - } - void Spells::purgeEffect(int effectId) { for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) @@ -412,7 +350,7 @@ namespace MWMechanics mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); } - void Spells::readState(const ESM::SpellState &state) + void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { @@ -436,31 +374,64 @@ namespace MWMechanics mUsedPowers[spell] = MWWorld::TimeStamp(it->second); } - for (std::map >::const_iterator it = - state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) + for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) { const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (!spell) continue; - mPermanentSpellEffects[spell] = MagicEffects(); - for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) + CorprusStats stats; + + int worsening = state.mCorprusSpells.at(it->first).mWorsenings; + + for (int i=0; imEffects.mList) { - mPermanentSpellEffects[spell].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude); + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = worsening; } + stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); + + creatureStats->addCorprusSpell(it->first, stats); } - mCorprusSpells.clear(); - for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + mSpellsChanged = true; + + // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. + for (std::map >::const_iterator it = + state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); - if (!spell) // Discard unavailable corprus spells + const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) continue; - mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; - mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } - mSpellsChanged = true; + // Import data only for player, other actors should not suffer from corprus worsening. + MWWorld::Ptr player = getPlayer(); + if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId()) + return; + + // Note: if target actor has the Restore attirbute effects, stats will be restored. + for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) + { + // Applied corprus effects are already in loaded stats modifiers + if (effectIt->mId == ESM::MagicEffect::FortifyAttribute) + { + AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); + attr.setModifier(attr.getModifier() - effectIt->mMagnitude); + attr.damage(-effectIt->mMagnitude); + creatureStats->setAttribute(effectIt->mArg, attr); + } + else if (effectIt->mId == ESM::MagicEffect::DrainAttribute) + { + AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); + attr.setModifier(attr.getModifier() + effectIt->mMagnitude); + attr.damage(effectIt->mMagnitude); + creatureStats->setAttribute(effectIt->mArg, attr); + } + } + } } void Spells::writeState(ESM::SpellState &state) const @@ -477,26 +448,5 @@ namespace MWMechanics for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) state.mUsedPowers[it->first->mId] = it->second.toEsm(); - - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - std::vector effectList; - for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) - { - ESM::SpellState::PermanentSpellEffectInfo info; - info.mId = effectIt->first.mId; - info.mArg = effectIt->first.mArg; - info.mMagnitude = effectIt->second.getModifier(); - - effectList.push_back(info); - } - state.mPermanentSpellEffects[it->first->mId] = effectList; - } - - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - state.mCorprusSpells[it->first->mId].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; - state.mCorprusSpells[it->first->mId].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); - } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index e635f4f56..a4a599f8b 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -22,6 +22,8 @@ namespace ESM namespace MWMechanics { + class CreatureStats; + class MagicEffects; /// \brief Spell list @@ -33,7 +35,8 @@ namespace MWMechanics public: typedef const ESM::Spell* SpellKey; - struct SpellParams { + struct SpellParams + { std::map mEffectRands; // std::set mPurgedEffects; // indices of purged effects }; @@ -41,27 +44,14 @@ namespace MWMechanics typedef std::map TContainer; typedef TContainer::const_iterator TIterator; - struct CorprusStats - { - static const int sWorseningPeriod = 24; - - int mWorsenings; - MWWorld::TimeStamp mNextWorsening; - }; - private: TContainer mSpells; - // spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed) - std::map mPermanentSpellEffects; - // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::map mUsedPowers; - std::map mCorprusSpells; - mutable bool mSpellsChanged; mutable MagicEffects mEffects; mutable std::map mSourcedEffects; @@ -73,9 +63,7 @@ namespace MWMechanics public: Spells(); - void worsenCorprus(const ESM::Spell* spell); static bool hasCorprusEffect(const ESM::Spell *spell); - const std::map & getCorprusSpells() const; void purgeEffect(int effectId); void purgeEffect(int effectId, const std::string & sourceId); @@ -128,7 +116,7 @@ namespace MWMechanics void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - void readState (const ESM::SpellState& state); + void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; }; } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index def3bbbc8..8b2f5c46c 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -31,9 +31,12 @@ namespace MWMechanics magicEffect = store.get().find(effect.mEffectID); bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; int duration = hasDuration ? effect.mDuration : 1; + if (!appliedOnce) + duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); @@ -91,8 +94,8 @@ namespace MWMechanics CreatureStats& stats = actor.getClass().getCreatureStats(actor); - int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); - int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index f53052a28..7f71cf9b1 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -227,39 +227,46 @@ namespace MWMechanics } AttributeValue::AttributeValue() : - mBase(0), mModifier(0), mDamage(0) + mBase(0.f), mModifier(0.f), mDamage(0.f) { } - int AttributeValue::getModified() const + float AttributeValue::getModified() const { - return std::max(0, mBase - (int) mDamage + mModifier); + return std::max(0.f, mBase - mDamage + mModifier); } - int AttributeValue::getBase() const + float AttributeValue::getBase() const { return mBase; } - int AttributeValue::getModifier() const + float AttributeValue::getModifier() const { return mModifier; } - void AttributeValue::setBase(int base) + void AttributeValue::setBase(float base) { mBase = base; } - void AttributeValue::setModifier(int mod) + void AttributeValue::setModifier(float mod) { mModifier = mod; } void AttributeValue::damage(float damage) { - mDamage += std::min(damage, (float)getModified()); + float threshold = mBase + mModifier; + + if (mDamage + damage > threshold) + mDamage = threshold; + else + mDamage += damage; } void AttributeValue::restore(float amount) { + if (mDamage <= 0) return; + mDamage -= std::min(mDamage, amount); } @@ -268,14 +275,14 @@ namespace MWMechanics return mDamage; } - void AttributeValue::writeState (ESM::StatState& state) const + void AttributeValue::writeState (ESM::StatState& state) const { state.mBase = mBase; state.mMod = mModifier; state.mDamage = mDamage; } - void AttributeValue::readState (const ESM::StatState& state) + void AttributeValue::readState (const ESM::StatState& state) { mBase = state.mBase; mModifier = state.mMod; @@ -296,13 +303,13 @@ namespace MWMechanics mProgress = progress; } - void SkillValue::writeState (ESM::StatState& state) const + void SkillValue::writeState (ESM::StatState& state) const { AttributeValue::writeState (state); state.mProgress = mProgress; } - void SkillValue::readState (const ESM::StatState& state) + void SkillValue::readState (const ESM::StatState& state) { AttributeValue::readState (state); mProgress = state.mProgress; diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 751b80b9c..5f49da48e 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -122,20 +122,20 @@ namespace MWMechanics class AttributeValue { - int mBase; - int mModifier; + float mBase; + float mModifier; float mDamage; // needs to be float to allow continuous damage public: AttributeValue(); - int getModified() const; - int getBase() const; - int getModifier() const; + float getModified() const; + float getBase() const; + float getModifier() const; - void setBase(int base); + void setBase(float base); - void setModifier(int mod); + void setModifier(float mod); // Maximum attribute damage is limited to the modified value. // Note: I think MW applies damage directly to mModified, since you can also @@ -145,8 +145,8 @@ namespace MWMechanics float getDamage() const; - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState (ESM::StatState& state) const; + void readState (const ESM::StatState& state); }; class SkillValue : public AttributeValue @@ -157,8 +157,8 @@ namespace MWMechanics float getProgress() const; void setProgress(float progress); - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState (ESM::StatState& state) const; + void readState (const ESM::StatState& state); }; inline bool operator== (const AttributeValue& left, const AttributeValue& right) diff --git a/apps/openmw/mwmechanics/typedaipackage.hpp b/apps/openmw/mwmechanics/typedaipackage.hpp new file mode 100644 index 000000000..c959f4d68 --- /dev/null +++ b/apps/openmw/mwmechanics/typedaipackage.hpp @@ -0,0 +1,28 @@ +#ifndef GAME_MWMECHANICS_TYPEDAIPACKAGE_H +#define GAME_MWMECHANICS_TYPEDAIPACKAGE_H + +#include "aipackage.hpp" + +namespace MWMechanics +{ + template + struct TypedAiPackage : public AiPackage + { + TypedAiPackage() : + AiPackage(T::getTypeId(), T::makeDefaultOptions()) {} + + TypedAiPackage(const Options& options) : + AiPackage(T::getTypeId(), options) {} + + template + TypedAiPackage(Derived*) : + AiPackage(Derived::getTypeId(), Derived::makeDefaultOptions()) {} + + virtual std::unique_ptr clone() const override + { + return std::make_unique(*static_cast(this)); + } + }; +} + +#endif diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 950630141..f95a67823 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -84,12 +84,12 @@ namespace MWPhysics assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); - for (std::map::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) + for (const auto& shape : mShapeInstance->mAnimatedShapes) { - int recIndex = it->first; - int shapeIndex = it->second; + int recIndex = shape.first; + int shapeIndex = shape.second; - std::map::iterator nodePathFound = mRecIndexToNodePath.find(recIndex); + auto nodePathFound = mRecIndexToNodePath.find(recIndex); if (nodePathFound == mRecIndexToNodePath.end()) { NifOsg::FindGroupByRecIndex visitor(recIndex); @@ -104,7 +104,7 @@ namespace MWPhysics } osg::NodePath nodePath = visitor.mFoundPath; nodePath.erase(nodePath.begin()); - nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first; + nodePathFound = mRecIndexToNodePath.emplace(recIndex, nodePath).first; } osg::NodePath& nodePath = nodePathFound->second; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index a205abeb4..d423830b1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,6 +1,7 @@ #include "physicssystem.hpp" #include +#include #include #include @@ -94,21 +95,21 @@ namespace MWPhysics if (mWaterCollisionObject.get()) mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); - for (HeightFieldMap::iterator it = mHeightFields.begin(); it != mHeightFields.end(); ++it) + for (auto& heightField : mHeightFields) { - mCollisionWorld->removeCollisionObject(it->second->getCollisionObject()); - delete it->second; + mCollisionWorld->removeCollisionObject(heightField.second->getCollisionObject()); + delete heightField.second; } - for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it) + for (auto& object : mObjects) { - mCollisionWorld->removeCollisionObject(it->second->getCollisionObject()); - delete it->second; + mCollisionWorld->removeCollisionObject(object.second->getCollisionObject()); + delete object.second; } - for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it) + for (auto& actor : mActors) { - delete it->second; + delete actor.second; } delete mCollisionWorld; @@ -396,6 +397,15 @@ namespace MWPhysics return osg::Vec3f(); } + osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const + { + const Object * physobject = getObject(object); + if (!physobject) return osg::BoundingBox(); + btVector3 min, max; + physobject->getCollisionObject()->getCollisionShape()->getAabb(physobject->getCollisionObject()->getWorldTransform(), min, max); + return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max)); + } + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); @@ -466,7 +476,7 @@ namespace MWPhysics return; Object *obj = new Object(ptr, shapeInstance); - mObjects.insert(std::make_pair(ptr, obj)); + mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj); @@ -508,10 +518,10 @@ namespace MWPhysics map.erase(found); } - for (CollisionMap::iterator it = map.begin(); it != map.end(); ++it) + for (auto& collision : map) { - if (it->second == old) - it->second = updated; + if (collision.second == old) + collision.second = updated; } } @@ -523,7 +533,7 @@ namespace MWPhysics Object* obj = found->second; obj->updatePtr(updated); mObjects.erase(found); - mObjects.insert(std::make_pair(updated, obj)); + mObjects.emplace(updated, obj); } ActorMap::iterator foundActor = mActors.find(old); @@ -532,7 +542,7 @@ namespace MWPhysics Actor* actor = foundActor->second; actor->updatePtr(updated); mActors.erase(foundActor); - mActors.insert(std::make_pair(updated, actor)); + mActors.emplace(updated, actor); } updateCollisionMapPtr(mStandingCollisions, old, updated); @@ -620,7 +630,8 @@ namespace MWPhysics } } - void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { + void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) + { osg::ref_ptr shape = mShapeManager->getShape(mesh); if (!shape) return; @@ -636,7 +647,7 @@ namespace MWPhysics } Actor* actor = new Actor(ptr, shape, mCollisionWorld); - mActors.insert(std::make_pair(ptr, actor)); + mActors.emplace(ptr, actor); } bool PhysicsSystem::toggleCollisionMode() @@ -647,7 +658,7 @@ namespace MWPhysics bool cmode = found->second->getCollisionMode(); cmode = !cmode; found->second->enableCollisionMode(cmode); - found->second->enableCollisionBody(cmode); + // NB: Collision body isn't disabled for vanilla TCL compatibility return cmode; } @@ -656,17 +667,16 @@ namespace MWPhysics void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement) { - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) + for(auto& movementItem : mMovementQueue) { - if(iter->first == ptr) + if (movementItem.first == ptr) { - iter->second = movement; + movementItem.second = movement; return; } } - mMovementQueue.push_back(std::make_pair(ptr, movement)); + mMovementQueue.emplace_back(ptr, movement); } void PhysicsSystem::clearQueuedMovement() @@ -695,27 +705,26 @@ namespace MWPhysics const MWWorld::Ptr player = MWMechanics::getPlayer(); const MWBase::World *world = MWBase::Environment::get().getWorld(); - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) + for(auto& movementItem : mMovementQueue) { - ActorMap::iterator foundActor = mActors.find(iter->first); + ActorMap::iterator foundActor = mActors.find(movementItem.first); if (foundActor == mActors.end()) // actor was already removed from the scene continue; Actor* physicActor = foundActor->second; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = iter->first.getCell(); + const MWWorld::CellStore *cell = movementItem.first.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + const MWMechanics::MagicEffects& effects = movementItem.first.getClass().getCreatureStats(movementItem.first).getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (!world->isUnderwater(iter->first.getCell(), osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) + if (!world->isUnderwater(movementItem.first.getCell(), osg::Vec3f(movementItem.first.getRefData().getPosition().asVec3()))) waterCollision = true; - else if (physicActor->getCollisionMode() && canMoveToWaterSurface(iter->first, waterlevel)) + else if (physicActor->getCollisionMode() && canMoveToWaterSurface(movementItem.first, waterlevel)) { const osg::Vec3f actorPosition = physicActor->getPosition(); physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel)); @@ -727,8 +736,8 @@ namespace MWPhysics // 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)); - bool flying = world->isFlying(iter->first); - bool swimming = world->isSwimming(iter->first); + bool flying = world->isFlying(movementItem.first); + bool swimming = world->isSwimming(movementItem.first); bool wasOnGround = physicActor->getOnGround(); osg::Vec3f position = physicActor->getPosition(); @@ -736,7 +745,7 @@ namespace MWPhysics bool positionChanged = false; for (int i=0; igetPtr(), physicActor, iter->second, mPhysicsDt, + position = MovementSolver::move(position, physicActor->getPtr(), physicActor, movementItem.second, mPhysicsDt, flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions); if (position != physicActor->getPosition()) positionChanged = true; @@ -750,14 +759,14 @@ namespace MWPhysics float heightDiff = position.z() - oldHeight; - MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + MWMechanics::CreatureStats& stats = movementItem.first.getClass().getCreatureStats(movementItem.first); bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround()); if (isStillOnGround || flying || swimming || slowFall < 1) - stats.land(iter->first == player && (flying || swimming)); + stats.land(movementItem.first == player && (flying || swimming)); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); - mMovementResults.push_back(std::make_pair(iter->first, interpolated)); + mMovementResults.emplace_back(movementItem.first, interpolated); } mMovementQueue.clear(); @@ -791,9 +800,9 @@ namespace MWPhysics bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { - for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) + for (const auto& standingActor : mStandingCollisions) { - if (it->first == actor && it->second == object) + if (standingActor.first == actor && standingActor.second == object) return true; } return false; @@ -801,10 +810,10 @@ namespace MWPhysics void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const { - for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) + for (const auto& standingActor : mStandingCollisions) { - if (it->second == object) - out.push_back(it->first); + if (standingActor.second == object) + out.push_back(standingActor.first); } } @@ -883,4 +892,11 @@ namespace MWPhysics mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } + + void PhysicsSystem::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "Physics Actors", mActors.size()); + stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); + stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); + } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 0f2ecc092..32d460b1d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "../mwworld/ptr.hpp" @@ -17,6 +18,7 @@ namespace osg { class Group; class Object; + class Stats; } namespace MWRender @@ -143,6 +145,9 @@ namespace MWPhysics /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; + /// Get bounding box in world space of the given object. + osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const; + /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); @@ -186,6 +191,8 @@ namespace MWPhysics bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: void updateWater(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e4122a1a9..31d4ae389 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -152,20 +152,8 @@ namespace } }; - NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) - { - NifOsg::TextKeyMap::const_iterator iter(keys.begin()); - for(;iter != keys.end();++iter) - { - if(iter->second.compare(0, groupname.size(), groupname) == 0 && - iter->second.compare(groupname.size(), 2, ": ") == 0) - break; - } - return iter; - } - - float calcAnimVelocity(const std::multimap& keys, - NifOsg::KeyframeController *nonaccumctrl, const osg::Vec3f& accum, const std::string &groupname) + float calcAnimVelocity(const NifOsg::TextKeyMap& keys, NifOsg::KeyframeController *nonaccumctrl, + const osg::Vec3f& accum, const std::string &groupname) { const std::string start = groupname+": start"; const std::string loopstart = groupname+": loop start"; @@ -180,7 +168,7 @@ namespace // but the animation velocity calculation uses the second one. // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. - NifOsg::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); + auto keyiter = keys.rbegin(); while(keyiter != keys.rend()) { if(keyiter->second == start || keyiter->second == loopstart) @@ -526,6 +514,9 @@ namespace MWRender if (mShadowUniform) stateset->addUniform(mShadowUniform); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; material->setColorMode(osg::Material::OFF); @@ -554,7 +545,7 @@ namespace MWRender ControllerMap mControllerMap[Animation::sNumBlendMasks]; - const std::multimap& getTextKeys() const; + const NifOsg::TextKeyMap& getTextKeys() const; }; void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) @@ -631,6 +622,8 @@ namespace MWRender , mTextKeyListener(nullptr) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) + , mUpperBodyYawRadians(0.f) + , mLegsYawRadians(0.f) , mHasMagicEffects(false) , mAlpha(1.f) { @@ -703,7 +696,7 @@ namespace MWRender return 0; } - const std::multimap &Animation::AnimSource::getTextKeys() const + const NifOsg::TextKeyMap &Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; } @@ -826,7 +819,7 @@ namespace MWRender for(;iter != mAnimSources.end();++iter) { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); - if(findGroupStart(keys, anim) != keys.end()) + if (keys.hasGroupStart(anim)) return true; } @@ -839,7 +832,7 @@ namespace MWRender { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); - NifOsg::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + const auto found = keys.findGroupStart(groupname); if(found != keys.end()) return found->first; } @@ -852,7 +845,7 @@ namespace MWRender { const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); - for(NifOsg::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) + for(auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey) { if(iterKey->second.compare(0, textKey.size(), textKey) == 0) return iterKey->first; @@ -862,8 +855,8 @@ namespace MWRender return -1.f; } - void Animation::handleTextKey(AnimState &state, const std::string &groupname, const std::multimap::const_iterator &key, - const std::multimap& map) + void Animation::handleTextKey(AnimState &state, const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, + const NifOsg::TextKeyMap& map) { const std::string &evt = key->second; @@ -948,7 +941,7 @@ namespace MWRender if (state.mPlaying) { - NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.getTime())); + auto textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); @@ -964,7 +957,7 @@ namespace MWRender if(state.getTime() >= state.mLoopStopTime) break; - NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.getTime())); + auto textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); @@ -983,7 +976,7 @@ namespace MWRender { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. - NifOsg::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); + auto groupend = keys.rbegin(); for(;groupend != keys.rend();++groupend) { if(groupend->second.compare(0, groupname.size(), groupname) == 0 && @@ -992,7 +985,7 @@ namespace MWRender } std::string starttag = groupname+": "+start; - NifOsg::TextKeyMap::const_reverse_iterator startkey(groupend); + auto startkey = groupend; while(startkey != keys.rend() && startkey->second != starttag) ++startkey; if(startkey == keys.rend() && start == "loop start") @@ -1006,7 +999,7 @@ namespace MWRender return false; const std::string stoptag = groupname+": "+stop; - NifOsg::TextKeyMap::const_reverse_iterator stopkey(groupend); + auto stopkey = groupend; while(stopkey != keys.rend() // We have to ignore extra garbage at the end. // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". @@ -1039,7 +1032,7 @@ namespace MWRender const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; - NifOsg::TextKeyMap::const_reverse_iterator key(groupend); + auto key = groupend; for (; key != startkey && key != keys.rend(); ++key) { if (key->first > state.getTime()) @@ -1121,7 +1114,7 @@ namespace MWRender const bool isPlayer = (mPtr == MWMechanics::getPlayer()); // remove all previous external controllers from the scene graph - for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) + for (auto it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) { osg::Node* node = it->first; node->removeUpdateCallback(it->second); @@ -1160,7 +1153,7 @@ namespace MWRender osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource if(!isPlayer || !vrOverride(active->first, it->first)) node->addUpdateCallback(it->second); - mActiveControllers.insert(std::make_pair(node, it->second)); + mActiveControllers.emplace_back(node, it->second); if (blendMask == 0 && node == mAccumRoot #ifdef USE_OPENXR @@ -1178,7 +1171,7 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } mAccumRoot->addUpdateCallback(mResetAccumRootCallback); - mActiveControllers.insert(std::make_pair(mAccumRoot, mResetAccumRootCallback)); + mActiveControllers.emplace_back(mAccumRoot, mResetAccumRootCallback); } } } @@ -1263,7 +1256,7 @@ namespace MWRender for(;animsrc != mAnimSources.rend();++animsrc) { const NifOsg::TextKeyMap &keys = (*animsrc)->getTextKeys(); - if(findGroupStart(keys, groupname) != keys.end()) + if (keys.hasGroupStart(groupname)) break; } if(animsrc == mAnimSources.rend()) @@ -1342,7 +1335,7 @@ namespace MWRender } const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); - NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime())); + auto textkey = textkeys.upperBound(state.getTime()); float timepassed = duration * state.mSpeedMult; while(state.mPlaying) @@ -1378,7 +1371,7 @@ namespace MWRender state.setTime(state.mLoopStartTime); state.mPlaying = true; - textkey = textkeys.lower_bound(state.getTime()); + textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); @@ -1405,13 +1398,36 @@ namespace MWRender updateEffects(); + const float epsilon = 0.001f; + float yawOffset = 0; + if (mRootController) + { + bool enable = std::abs(mLegsYawRadians) > epsilon; + mRootController->setEnabled(enable); + if (enable) + { + mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1))); + yawOffset = mLegsYawRadians; + } + } + if (mSpineController) + { + float yaw = mUpperBodyYawRadians - yawOffset; + bool enable = std::abs(yaw) > epsilon; + mSpineController->setEnabled(enable); + if (enable) + { + mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1))); + yawOffset = mUpperBodyYawRadians; + } + } if (mHeadController) { - const float epsilon = 0.001f; - bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon); + float yaw = mHeadYawRadians - yawOffset; + bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon); mHeadController->setEnabled(enable); if (enable) - mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(mHeadYawRadians, osg::Vec3f(0,0,1))); + mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1))); } // Scripted animations should not cause movement @@ -1443,7 +1459,7 @@ namespace MWRender osg::Group* sheathParent = findVisitor.mFoundNode; if (sheathParent) { - osg::Node* copy = osg::clone(nodePair.first, osg::CopyOp::DEEP_COPY_NODES); + osg::Node* copy = static_cast(nodePair.first->clone(osg::CopyOp::DEEP_COPY_NODES)); sheathParent->addChild(copy); } } @@ -1815,31 +1831,16 @@ namespace MWRender if (mTransparencyUpdater == nullptr) { mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); - mObjectRoot->addUpdateCallback(mTransparencyUpdater); + mObjectRoot->addCullCallback(mTransparencyUpdater); } else mTransparencyUpdater->setAlpha(alpha); } else { - mObjectRoot->removeUpdateCallback(mTransparencyUpdater); + mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; - mObjectRoot->setStateSet(nullptr); } - - setRenderBin(); - } - - void Animation::setRenderBin() - { - if (mAlpha != 1.f) - { - osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); - } - else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) - stateset->setRenderBinToInherit(); } void Animation::setLightEffect(float effect) @@ -1887,35 +1888,38 @@ namespace MWRender void Animation::addControllers() { - mHeadController = nullptr; + mHeadController = addRotateController("bip01 head"); + mSpineController = addRotateController("bip01 spine1"); + mRootController = addRotateController("bip01"); + } - if (mPtr.getClass().isBipedal(mPtr)) + RotateController* Animation::addRotateController(std::string bone) + { + auto iter = getNodeMap().find(bone); + if (iter == getNodeMap().end()) + return nullptr; + osg::MatrixTransform* node = iter->second; + + bool foundKeyframeCtrl = false; + osg::Callback* cb = node->getUpdateCallback(); + while (cb) { - NodeMap::const_iterator found = getNodeMap().find("bip01 head"); - if (found != getNodeMap().end()) + if (dynamic_cast(cb)) { - osg::MatrixTransform* node = found->second; - - bool foundKeyframeCtrl = false; - osg::Callback* cb = node->getUpdateCallback(); - while (cb) - { - if (dynamic_cast(cb)) - { - foundKeyframeCtrl = true; - break; - } - cb = cb->getNestedCallback(); - } - - if (foundKeyframeCtrl) - { - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.insert(std::make_pair(node, mHeadController)); - } + foundKeyframeCtrl = true; + break; } + cb = cb->getNestedCallback(); } + // Without KeyframeController the orientation will not be reseted each frame, so + // RotateController shouldn't be used for such nodes. + if (!foundKeyframeCtrl) + return nullptr; + + RotateController* controller = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(controller); + mActiveControllers.emplace_back(node, controller); + return controller; } void Animation::setHeadPitch(float pitchRadians) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a5600fa90..9e5425715 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -5,6 +5,9 @@ #include #include +#include + +#include namespace ESM { @@ -147,8 +150,8 @@ public: class TextKeyListener { public: - virtual void handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, - const std::multimap& map) = 0; + virtual void handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, + const NifOsg::TextKeyMap& map) = 0; virtual ~TextKeyListener() = default; }; @@ -246,8 +249,7 @@ protected: // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. - typedef std::multimap, osg::ref_ptr > ControllerMap; - ControllerMap mActiveControllers; + std::vector, osg::ref_ptr>> mActiveControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; @@ -265,8 +267,15 @@ protected: TextKeyListener* mTextKeyListener; osg::ref_ptr mHeadController; + osg::ref_ptr mSpineController; + osg::ref_ptr mRootController; float mHeadYawRadians; float mHeadPitchRadians; + float mUpperBodyYawRadians; + float mLegsYawRadians; + + RotateController* addRotateController(std::string bone); + bool mHasMagicEffects; osg::ref_ptr mGlowLight; @@ -296,12 +305,12 @@ protected: * the marker is not found, or if the markers are the same, it returns * false. */ - bool reset(AnimState &state, const std::multimap &keys, + bool reset(AnimState &state, const NifOsg::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback); - void handleTextKey(AnimState &state, const std::string &groupname, const std::multimap::const_iterator &key, - const std::multimap& map); + void handleTextKey(AnimState &state, const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, + const NifOsg::TextKeyMap& map); /** Sets the root model of the object. * @@ -334,9 +343,6 @@ protected: */ virtual void addControllers(); - /// Set the render bin for this animation's object root. May be customized by subclasses. - virtual void setRenderBin(); - public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); @@ -478,6 +484,12 @@ public: virtual void setHeadYaw(float yawRadians); virtual float getHeadPitch() const; virtual float getHeadYaw() const; + + virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; } + virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; } + virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; } + virtual float getLegsYawRadians() const { return mLegsYawRadians; } + virtual void setAccurateAiming(bool enabled) {} virtual bool canBeHarvested() const { return false; } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index a19622c40..b767b8f3d 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" @@ -16,6 +17,9 @@ #include "../mwvr/vrenvironment.hpp" #endif +#include "../mwmechanics/drawstate.hpp" +#include "../mwmechanics/npcstats.hpp" + #include "npcanimation.hpp" #include @@ -59,11 +63,14 @@ namespace MWRender mFurthest(800.f), mIsNearest(false), mHeight(124.f), - mMaxCameraDistance(192.f), + mBaseCameraDistance(192.f), mVanityToggleQueued(false), mVanityToggleQueuedValue(false), mViewModeToggleQueued(false), - mCameraDistance(0.f) + mCameraDistance(0.f), + mThirdPersonMode(ThirdPersonViewMode::Standard), + mOverShoulderOffset(osg::Vec2f(30.0f, -10.0f)), + mSmoothTransitionToCombatMode(0.f) { mVanity.enabled = false; mVanity.allowed = true; @@ -77,7 +84,7 @@ namespace MWRender mMainCam.yaw = 0.f; mMainCam.offset = 400.f; - mCameraDistance = mMaxCameraDistance; + mCameraDistance = mBaseCameraDistance; mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); @@ -93,7 +100,7 @@ namespace MWRender return mTrackingPtr; } - osg::Vec3d Camera::getFocalPoint() + osg::Vec3d Camera::getFocalPoint() const { const osg::Node* trackNode = mTrackingNode; if (!trackNode) @@ -108,7 +115,15 @@ namespace MWRender osg::Vec3d position = worldMat.getTrans(); if (!isFirstPerson()) + { position.z() += mHeight * mHeightScale; + + // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. + // Needed because character's head can be a bit higher than collision area. + position.z() -= 10.f; + + position += getFocalPointOffset() + mFocalPointAdjustment; + } return position; } @@ -117,10 +132,38 @@ namespace MWRender return mCamera; } + void Camera::updateCamera() { updateCamera(mCamera); } + + osg::Vec3d Camera::getFocalPointOffset() const + { + osg::Vec3d offset(0, 0, 10.f); + if (mThirdPersonMode == ThirdPersonViewMode::OverShoulder && !mPreviewMode && !mVanity.enabled) + { + float horizontalOffset = mOverShoulderOffset.x() * (1.f - mSmoothTransitionToCombatMode); + float verticalOffset = mSmoothTransitionToCombatMode * 15.f + (1.f - mSmoothTransitionToCombatMode) * mOverShoulderOffset.y(); + + offset.x() += horizontalOffset * cos(getYaw()); + offset.y() += horizontalOffset * sin(getYaw()); + offset.z() += verticalOffset; + } + return offset; + } + + void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const + { + focal = getFocalPoint(); + osg::Vec3d offset(0,0,0); + if (!isFirstPerson()) + { + osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); + } + camera = focal + offset; + } void Camera::updateCamera(osg::Camera *cam) { @@ -128,9 +171,10 @@ namespace MWRender return; osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getRoll(), osg::Vec3d(0, 1, 0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + osg::Vec3d focal, position; + getPosition(focal, position); osg::Vec3d position = getFocalPoint(); - #ifdef USE_OPENXR auto* inputManager = MWVR::Environment::get().getInputManager(); if (inputManager) @@ -138,11 +182,8 @@ namespace MWRender position += inputManager->headOffset(); } #else - osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0); - position += offset; #endif - osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); @@ -197,14 +238,38 @@ namespace MWRender if (paused) return; - // only show the crosshair in game mode and in first person mode. + // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - wm->showCrosshair(!wm->isGuiMode() && (mFirstPersonView && !mVanity.enabled && !mPreviewMode)); + wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode + && (mFirstPersonView || mThirdPersonMode != ThirdPersonViewMode::Standard)); if(mVanity.enabled) { rotateCamera(0.f, 0.f, osg::DegreesToRadians(3.f * duration), true); } + + updateSmoothTransitionToCombatMode(duration); + } + + void Camera::setOverShoulderOffset(float horizontal, float vertical) + { + mOverShoulderOffset = osg::Vec2f(horizontal, vertical); + } + + void Camera::updateSmoothTransitionToCombatMode(float duration) + { + bool combatMode = true; + if (mTrackingPtr.getClass().isActor()) + combatMode = mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; + float speed = ((combatMode ? 1.f : 0.f) - mSmoothTransitionToCombatMode) * 5; + if (speed != 0) + speed += speed > 0 ? 1 : -1; + + mSmoothTransitionToCombatMode += speed * duration; + if (mSmoothTransitionToCombatMode > 1) + mSmoothTransitionToCombatMode = 1; + if (mSmoothTransitionToCombatMode < 0) + mSmoothTransitionToCombatMode = 0; } void Camera::toggleViewMode(bool force) @@ -219,6 +284,9 @@ namespace MWRender else mViewModeToggleQueued = false; + if (mTrackingPtr.getClass().isActor()) + mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0); + mFirstPersonView = !mFirstPersonView; processViewChange(); } @@ -296,7 +364,7 @@ namespace MWRender mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } - float Camera::getYaw() + float Camera::getYaw() const { if(mVanity.enabled || mPreviewMode) return mPreviewCam.yaw; @@ -346,7 +414,7 @@ namespace MWRender #endif } - float Camera::getPitch() + float Camera::getPitch() const { if (mVanity.enabled || mPreviewMode) { return mPreviewCam.pitch; @@ -382,7 +450,7 @@ namespace MWRender return mCameraDistance; } - void Camera::setCameraDistance(float dist, bool adjust, bool override) + void Camera::setBaseCameraDistance(float dist, bool adjust) { if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) return; @@ -390,34 +458,55 @@ namespace MWRender mIsNearest = false; if (adjust) - dist += mCameraDistance; + { + if (mVanity.enabled || mPreviewMode) + dist += mCameraDistance; + else + dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance); + } + - if (dist >= mFurthest) { + if (dist >= mFurthest) dist = mFurthest; - } else if (!override && dist < 10.f) { - dist = 10.f; - } else if (override && dist <= mNearest) { + else if (dist <= mNearest) + { dist = mNearest; mIsNearest = true; } + + if (mVanity.enabled || mPreviewMode) + mPreviewCam.offset = dist; + else if (!mFirstPersonView) + mBaseCameraDistance = dist; + setCameraDistance(); + } + + void Camera::setCameraDistance(float dist, bool adjust) + { + if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) + return; + + if (adjust) dist += mCameraDistance; + + if (dist >= mFurthest) + dist = mFurthest; + else if (dist < 10.f) + dist = 10.f; mCameraDistance = dist; + } - if (override) { - if (mVanity.enabled || mPreviewMode) { - mPreviewCam.offset = mCameraDistance; - } else if (!mFirstPersonView) { - mMaxCameraDistance = mCameraDistance; - } - } + float Camera::getCameraDistanceCorrection() const + { + return mThirdPersonMode != ThirdPersonViewMode::Standard ? std::max(-getPitch(), 0.f) * 50.f : 0; } void Camera::setCameraDistance() { - if (mVanity.enabled || mPreviewMode) { + if (mVanity.enabled || mPreviewMode) mCameraDistance = mPreviewCam.offset; - } else if (!mFirstPersonView) { - mCameraDistance = mMaxCameraDistance; - } + else if (!mFirstPersonView) + mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); + mFocalPointAdjustment = osg::Vec3d(); } void Camera::setAnimation(NpcAnimation *anim) @@ -462,22 +551,12 @@ namespace MWRender #endif } - void Camera::getPosition(osg::Vec3f &focal, osg::Vec3f &camera) - { - focal = getFocalPoint(); - - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); - - osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0); - camera = focal + offset; - } - - bool Camera::isVanityOrPreviewModeEnabled() + bool Camera::isVanityOrPreviewModeEnabled() const { return mPreviewMode || mVanity.enabled; } - bool Camera::isNearest() + bool Camera::isNearest() const { return mIsNearest; } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 5c9e0ae95..7b480b27c 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -23,6 +23,10 @@ namespace MWRender /// \brief Camera control class Camera { + public: + enum class ThirdPersonViewMode {Standard, OverShoulder}; + + private: struct CamData { float roll, pitch, yaw, offset; }; @@ -45,7 +49,7 @@ namespace MWRender bool enabled, allowed; } mVanity; - float mHeight, mMaxCameraDistance; + float mHeight, mBaseCameraDistance; CamData mMainCam, mPreviewCam; bool mVanityToggleQueued; @@ -54,6 +58,16 @@ namespace MWRender float mCameraDistance; + ThirdPersonViewMode mThirdPersonMode; + osg::Vec2f mOverShoulderOffset; + osg::Vec3d mFocalPointAdjustment; + + // Makes sense only if mThirdPersonMode is OverShoulder. Can be in range [0, 1]. + // Used for smooth transition from non-combat camera position (0) to combat camera position (1). + float mSmoothTransitionToCombatMode; + void updateSmoothTransitionToCombatMode(float duration); + float getCameraDistanceCorrection() const; + osg::ref_ptr mUpdateCallback; public: @@ -62,6 +76,9 @@ namespace MWRender MWWorld::Ptr getTrackingPtr() const; + void setThirdPersonViewMode(ThirdPersonViewMode mode) { mThirdPersonMode = mode; } + void setOverShoulderOffset(float horizontal, float vertical); + /// Update the view matrix of \a cam void updateCamera(osg::Camera* cam); @@ -75,13 +92,14 @@ namespace MWRender /// \param rot Rotation angles in radians void rotateCamera(float pitch, float roll, float yaw, bool adjust); - float getYaw(); + float getYaw() const; void setYaw(float angle); float getRoll(); + float getPitch() const; void setRoll(float angle); - float getPitch(); + float getPitch() const; void setPitch(float angle); /// Attach camera to object @@ -106,29 +124,34 @@ namespace MWRender void update(float duration, bool paused=false); + /// Set base camera distance for current mode. Don't work on 1st person view. + /// \param adjust Indicates should distance be adjusted or set. + void setBaseCameraDistance(float dist, bool adjust = false); + /// Set camera distance for current mode. Don't work on 1st person view. /// \param adjust Indicates should distance be adjusted or set. - /// \param override If true new distance will be used as default. - /// If false, default distance can be restored with setCameraDistance(). - void setCameraDistance(float dist, bool adjust = false, bool override = true); + /// Default distance can be restored with setCameraDistance(). + void setCameraDistance(float dist, bool adjust = false); - /// Restore default camera distance for current mode. + /// Restore default camera distance and offset for current mode. void setCameraDistance(); float getCameraDistance() const; void setAnimation(NpcAnimation *anim); - osg::Vec3d getFocalPoint(); - osg::Camera* getOsgCamera(); + osg::Vec3d getFocalPoint() const; + osg::Vec3d getFocalPointOffset() const; + void adjustFocalPoint(osg::Vec3d adjustment) { mFocalPointAdjustment = adjustment; } + /// Stores focal and camera world positions in passed arguments - void getPosition(osg::Vec3f &focal, osg::Vec3f &camera); + void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; - bool isVanityOrPreviewModeEnabled(); + bool isVanityOrPreviewModeEnabled() const; - bool isNearest(); + bool isNearest() const; }; } diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp new file mode 100644 index 000000000..837e6ad04 --- /dev/null +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -0,0 +1,104 @@ +#include "fogmanager.hpp" + +#include + +#include +#include +#include +#include + +namespace +{ + float DLLandFogStart; + float DLLandFogEnd; + float DLUnderwaterFogStart; + float DLUnderwaterFogEnd; + float DLInteriorFogStart; + float DLInteriorFogEnd; +} + +namespace MWRender +{ + FogManager::FogManager() + : mLandFogStart(0.f) + , mLandFogEnd(std::numeric_limits::max()) + , mUnderwaterFogStart(0.f) + , mUnderwaterFogEnd(std::numeric_limits::max()) + , mFogColor(osg::Vec4f()) + , mDistantFog(Settings::Manager::getBool("use distant fog", "Fog")) + , mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor")) + , mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight")) + , mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog")) + { + DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog"); + DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog"); + DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog"); + DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog"); + DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog"); + DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog"); + } + + void FogManager::configure(float viewDistance, const ESM::Cell *cell) + { + osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); + + if (mDistantFog) + { + float density = std::max(0.2f, cell->mAmbi.mFogDensity); + mLandFogStart = DLInteriorFogEnd * (1.0f - density) + DLInteriorFogStart*density; + mLandFogEnd = DLInteriorFogEnd; + mUnderwaterFogStart = DLUnderwaterFogStart; + mUnderwaterFogEnd = DLUnderwaterFogEnd; + mFogColor = color; + } + else + configure(viewDistance, cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); + } + + void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) + { + if (mDistantFog) + { + mLandFogStart = dlFactor * (DLLandFogStart - dlOffset * DLLandFogEnd); + mLandFogEnd = dlFactor * (1.0f - dlOffset) * DLLandFogEnd; + mUnderwaterFogStart = DLUnderwaterFogStart; + mUnderwaterFogEnd = DLUnderwaterFogEnd; + } + else + { + if (fogDepth == 0.0) + { + mLandFogStart = 0.0f; + mLandFogEnd = std::numeric_limits::max(); + } + else + { + mLandFogStart = viewDistance * (1 - fogDepth); + mLandFogEnd = viewDistance; + } + mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog); + mUnderwaterFogEnd = std::min(viewDistance, 6666.f); + } + mFogColor = color; + } + + float FogManager::getFogStart(bool isUnderwater) const + { + return isUnderwater ? mUnderwaterFogStart : mLandFogStart; + } + + float FogManager::getFogEnd(bool isUnderwater) const + { + return isUnderwater ? mUnderwaterFogEnd : mLandFogEnd; + } + + osg::Vec4f FogManager::getFogColor(bool isUnderwater) const + { + if (isUnderwater) + { + return mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight); + } + + return mFogColor; + } +} diff --git a/apps/openmw/mwrender/fogmanager.hpp b/apps/openmw/mwrender/fogmanager.hpp new file mode 100644 index 000000000..c3efd06ab --- /dev/null +++ b/apps/openmw/mwrender/fogmanager.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_MWRENDER_FOGMANAGER_H +#define OPENMW_MWRENDER_FOGMANAGER_H + +#include + +namespace ESM +{ + struct Cell; +} + +namespace MWRender +{ + class FogManager + { + public: + FogManager(); + + void configure(float viewDistance, const ESM::Cell *cell); + void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color); + + osg::Vec4f getFogColor(bool isUnderwater) const; + float getFogStart(bool isUnderwater) const; + float getFogEnd(bool isUnderwater) const; + + private: + float mLandFogStart; + float mLandFogEnd; + float mUnderwaterFogStart; + float mUnderwaterFogEnd; + osg::Vec4f mFogColor; + bool mDistantFog; + + osg::Vec4f mUnderwaterColor; + float mUnderwaterWeight; + float mUnderwaterIndoorFog; + }; +} + +#endif diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 416a753eb..aaa797ef1 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -74,7 +74,7 @@ LocalMap::LocalMap(osg::Group* root) : mRoot(root) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(Constants::CellSizeInUnits) - , mCellDistance(Settings::Manager::getInt("local map cell distance", "Map")) + , mCellDistance(Constants::CellGridRadius) , mAngle(0.f) , mInterior(false) { @@ -168,11 +168,10 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) { osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -360,11 +359,6 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, osg::Vec3d(0,1,0), zmin, zmax); - camera->getOrCreateUserDataContainer()->addDescription("NoTerrainLod"); - std::ostringstream stream; - stream << x << " " << y; - camera->getOrCreateUserDataContainer()->addDescription(stream.str()); - setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index cdcd7457d..d3b0b1bd0 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -435,12 +435,10 @@ void NpcAnimation::setRenderBin() osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } - - osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); - stateset->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } - else - Animation::setRenderBin(); + else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) + stateset->setRenderBinToInherit(); } void NpcAnimation::rebuild() @@ -956,7 +954,7 @@ void NpcAnimation::addControllers() osg::MatrixTransform* node = found->second.get(); mFirstPersonNeckController = new NeckController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); - mActiveControllers.emplace(node, mFirstPersonNeckController); + mActiveControllers.emplace_back(node, mFirstPersonNeckController); } } else if (mViewMode != VM_HeadOnly) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7879ab887..d0f23f773 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -93,7 +93,7 @@ protected: void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); - virtual void setRenderBin(); + void setRenderBin(); osg::ref_ptr mFirstPersonNeckController; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp new file mode 100644 index 000000000..4810b2385 --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -0,0 +1,782 @@ +#include "objectpaging.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + + bool typeFilter(int type, bool far) + { + switch (type) + { + case ESM::REC_STAT: + case ESM::REC_ACTI: + case ESM::REC_DOOR: + return true; + case ESM::REC_CONT: + return !far; + + default: + return false; + } + } + + std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + case ESM::REC_ACTI: + return store.get().searchStatic(id)->mModel; + case ESM::REC_DOOR: + return store.get().searchStatic(id)->mModel; + case ESM::REC_CONT: + return store.get().searchStatic(id)->mModel; + default: + return std::string(); + } + } + + osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + if (activeGrid && !mActiveGrid) + return nullptr; + + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback + { + public: + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const + { + return true; + } + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const + { + return (node->getDataVariance() != osg::Object::DYNAMIC); + } + }; + + class CopyOp : public osg::CopyOp + { + public: + bool mOptimizeBillboards = true; + float mSqrDistance = 0.f; + osg::Vec3f mViewVector; + mutable std::vector mNodePath; + + void copy(const osg::Node* toCopy, osg::Group* attachTo) + { + const osg::Group* groupToCopy = toCopy->asGroup(); + if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy) + attachTo->addChild(operator()(toCopy)); + else + { + for (unsigned int i=0; igetNumChildren(); ++i) + attachTo->addChild(operator()(groupToCopy->getChild(i))); + } + } + + virtual osg::Node* operator() (const osg::Node* node) const + { + if (const osg::Drawable* d = node->asDrawable()) + return operator()(d); + + if (dynamic_cast(node)) + return nullptr; + if (dynamic_cast(node)) + return nullptr; + + if (const osg::Switch* sw = node->asSwitch()) + { + osg::Group* n = new osg::Group; + for (unsigned int i=0; igetNumChildren(); ++i) + if (sw->getValue(i)) + n->addChild(operator()(sw->getChild(i))); + n->setDataVariance(osg::Object::STATIC); + return n; + } + if (const osg::LOD* lod = dynamic_cast(node)) + { + osg::Group* n = new osg::Group; + for (unsigned int i=0; igetNumChildren(); ++i) + if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) + n->addChild(operator()(lod->getChild(i))); + n->setDataVariance(osg::Object::STATIC); + return n; + } + + mNodePath.push_back(node); + + osg::Node* cloned = static_cast(node->clone(*this)); + cloned->setDataVariance(osg::Object::STATIC); + cloned->setUserDataContainer(nullptr); + cloned->setName(""); + + mNodePath.pop_back(); + + handleCallbacks(node, cloned); + + return cloned; + } + void handleCallbacks(const osg::Node* node, osg::Node *cloned) const + { + for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback()) + { + if (callback->className() == std::string("BillboardCallback")) + { + if (mOptimizeBillboards) + { + handleBillboard(cloned); + continue; + } + else + cloned->setDataVariance(osg::Object::DYNAMIC); + } + + if (node->getCullCallback()->getNestedCallback()) + { + osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); + clonedCallback->setNestedCallback(nullptr); + cloned->addCullCallback(clonedCallback); + } + else + cloned->addCullCallback(const_cast(callback)); + } + } + void handleBillboard(osg::Node* node) const + { + osg::Transform* transform = node->asTransform(); + if (!transform) return; + osg::MatrixTransform* matrixTransform = transform->asMatrixTransform(); + if (!matrixTransform) return; + + osg::Matrix worldToLocal = osg::Matrix::identity(); + for (auto pathNode : mNodePath) + if (const osg::Transform* t = pathNode->asTransform()) + t->computeWorldToLocalMatrix(worldToLocal, nullptr); + worldToLocal = osg::Matrix::orthoNormal(worldToLocal); + + osg::Matrix billboardMatrix; + osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans()); + viewVector.normalize(); + osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1); + right.normalize(); + osg::Vec3f up = right ^ viewVector; + up.normalize(); + billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up); + billboardMatrix.invert(billboardMatrix); + + const osg::Matrix& oldMatrix = matrixTransform->getMatrix(); + float mag[3]; // attempt to preserve scale + for (int i=0;i<3;++i) + mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i)); + osg::Matrix newMatrix; + worldToLocal.setTrans(0,0,0); + newMatrix *= worldToLocal; + newMatrix.preMult(billboardMatrix); + newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2])); + newMatrix.setTrans(oldMatrix.getTrans()); + + matrixTransform->setMatrix(newMatrix); + } + virtual osg::Drawable* operator() (const osg::Drawable* drawable) const + { + if (dynamic_cast(drawable)) + return nullptr; + + if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) + return operator()(rig->getSourceGeometry()); + if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) + return operator()(morph->getSourceGeometry()); + + if (getCopyFlags() & DEEP_COPY_DRAWABLES) + { + osg::Drawable* d = static_cast(drawable->clone(*this)); + d->setDataVariance(osg::Object::STATIC); + d->setUserDataContainer(nullptr); + d->setName(""); + return d; + } + else + return const_cast(drawable); + } + virtual osg::Callback* operator() (const osg::Callback* callback) const + { + return nullptr; + } + }; + + class TemplateRef : public osg::Object + { + public: + TemplateRef() {} + TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} + META_Object(MWRender, TemplateRef) + std::vector> mObjects; + }; + + class RefnumSet : public osg::Object + { + public: + RefnumSet(){} + RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {} + META_Object(MWRender, RefnumSet) + std::set mRefnums; + }; + + class AnalyzeVisitor : public osg::NodeVisitor + { + public: + AnalyzeVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mCurrentStateSet(nullptr) {} + + typedef std::unordered_map StateSetCounter; + struct Result + { + StateSetCounter mStateSetCounter; + unsigned int mNumVerts = 0; + }; + + virtual void apply(osg::Node& node) + { + if (node.getStateSet()) + mCurrentStateSet = node.getStateSet(); + traverse(node); + } + virtual void apply(osg::Geometry& geom) + { + mResult.mNumVerts += geom.getVertexArray()->getNumElements(); + ++mResult.mStateSetCounter[mCurrentStateSet]; + ++mGlobalStateSetCounter[mCurrentStateSet]; + } + Result retrieveResult() + { + Result result = mResult; + mResult = Result(); + mCurrentStateSet = nullptr; + return result; + } + void addInstance(const Result& result) + { + for (auto pair : result.mStateSetCounter) + mGlobalStateSetCounter[pair.first] += pair.second; + } + float getMergeBenefit(const Result& result) + { + if (result.mStateSetCounter.empty()) return 1; + float mergeBenefit = 0; + for (auto pair : result.mStateSetCounter) + { + mergeBenefit += mGlobalStateSetCounter[pair.first]; + } + mergeBenefit /= result.mStateSetCounter.size(); + return mergeBenefit; + } + + Result mResult; + osg::StateSet* mCurrentStateSet; + StateSetCounter mGlobalStateSetCounter; + }; + + class DebugVisitor : public osg::NodeVisitor + { + public: + DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} + virtual void apply(osg::Drawable& node) + { + osg::ref_ptr m (new osg::Material); + osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); + color.normalize(); + m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); + m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); + m->setColorMode(osg::Material::OFF); + m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color)); + osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; + stateset->setAttribute(m); + stateset->addUniform(new osg::Uniform("colorMode", 0)); + node.setStateSet(stateset); + } + }; + + class AddRefnumMarkerVisitor : public osg::NodeVisitor + { + public: + AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {} + ESM::RefNum mRefnum; + virtual void apply(osg::Geometry &node) + { + osg::ref_ptr marker (new RefnumMarker); + marker->mRefnum = mRefnum; + if (osg::Array* array = node.getVertexArray()) + marker->mNumVertices = array->getNumElements(); + node.getOrCreateUserDataContainer()->addUserObject(marker); + } + }; + + ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mRefTrackerLocked(false) + { + mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); + mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain"); + mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); + mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); + mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); + mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); + } + + osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + osg::Vec3f relativeViewPoint = viewPoint - worldCenter; + + std::map refs; + std::vector esm; + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + for (size_t i=0; imContextList.size(); ++i) + { + try + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size()<=index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + int type = store.findStatic(ref.mRefID); + if (!typeFilter(type,size>=2)) continue; + if (deleted) { refs.erase(ref.mRefNum); continue; } + refs[ref.mRefNum] = ref; + } + } + catch (std::exception& e) + { + continue; + } + } + for (ESM::CellRefTracker::const_iterator it = cell->mLeasedRefs.begin(); it != cell->mLeasedRefs.end(); ++it) + { + ESM::CellRef ref = it->first; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + bool deleted = it->second; + if (deleted) { refs.erase(ref.mRefNum); continue; } + int type = store.findStatic(ref.mRefID); + if (!typeFilter(type,size>=2)) continue; + refs[ref.mRefNum] = ref; + } + } + } + + if (activeGrid) + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + for (auto ref : getRefTracker().mBlacklist) + refs.erase(ref); + } + + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + struct InstanceList + { + std::vector mInstances; + AnalyzeVisitor::Result mAnalyzeResult; + bool mNeedCompile = false; + }; + typedef std::map, InstanceList> NodeMap; + NodeMap nodes; + osg::ref_ptr refnumSet = activeGrid ? new RefnumSet : nullptr; + AnalyzeVisitor analyzeVisitor; + float minSize = mMinSize; + if (mMinSizeMergeFactor) + minSize *= mMinSizeMergeFactor; + for (const auto& pair : refs) + { + const ESM::CellRef& ref = pair.second; + + osg::Vec3f pos = ref.mPos.asVec3(); + if (size < 1.f) + { + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + continue; + } + + float dSqr = (viewPoint - pos).length2(); + if (!activeGrid) + { + OpenThreads::ScopedLock lock(mSizeCacheMutex); + SizeCache::iterator found = mSizeCache.find(pair.first); + if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize) + continue; + } + + if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker") + continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + + int type = store.findStatic(ref.mRefID); + std::string model = getModel(type, ref.mRefID, store); + if (model.empty()) continue; + model = "meshes/" + model; + + if (activeGrid && type != ESM::REC_STAT) + { + model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); + std::string kfname = Misc::StringUtils::lowerCase(model); + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + { + kfname.replace(kfname.size()-4, 4, ".kf"); + if (mSceneManager->getVFS()->exists(kfname)) + continue; + } + } + + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); + + if (activeGrid) + { + if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) + continue; + else + refnumSet->mRefnums.insert(pair.first); + } + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (getRefTracker().mDisabled.count(pair.first)) + continue; + } + + float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale; + if (radius2 < dSqr*minSize*minSize && !activeGrid) + { + OpenThreads::ScopedLock lock(mSizeCacheMutex); + mSizeCache[pair.first] = radius2; + continue; + } + + auto emplaced = nodes.emplace(cnode, InstanceList()); + if (emplaced.second) + { + const_cast(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor + emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult(); + emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3; + } + else + analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult); + emplaced.first->second.mInstances.push_back(&ref); + } + + osg::ref_ptr group = new osg::Group; + osg::ref_ptr mergeGroup = new osg::Group; + osg::ref_ptr templateRefs = new TemplateRef; + osgUtil::StateToCompile stateToCompile(0, nullptr); + CopyOp copyop; + for (const auto& pair : nodes) + { + const osg::Node* cnode = pair.first; + + const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult; + + float mergeCost = analyzeResult.mNumVerts * size; + float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor; + bool merge = mergeBenefit > mergeCost; + + float minSizeMerged = mMinSize; + float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1; + float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2; + if (minSizeMergeFactor2 > 0) + minSizeMerged *= minSizeMergeFactor2; + + unsigned int numinstances = 0; + for (auto cref : pair.second.mInstances) + { + const ESM::CellRef& ref = *cref; + osg::Vec3f pos = ref.mPos.asVec3(); + + if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged) + continue; + + osg::Matrixf matrix; + matrix.preMultTranslate(pos - worldCenter); + matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * + osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) * + osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) ); + matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale)); + osg::ref_ptr trans = new osg::MatrixTransform(matrix); + trans->setDataVariance(osg::Object::STATIC); + + copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); + copyop.mOptimizeBillboards = (size > 1/4.f); + copyop.mNodePath.push_back(trans); + copyop.mSqrDistance = (viewPoint - pos).length2(); + copyop.mViewVector = (viewPoint - worldCenter); + copyop.copy(cnode, trans); + copyop.mNodePath.pop_back(); + + if (activeGrid) + { + if (merge) + { + AddRefnumMarkerVisitor visitor(ref.mRefNum); + trans->accept(visitor); + } + else + { + osg::ref_ptr marker = new RefnumMarker; marker->mRefnum = ref.mRefNum; + trans->getOrCreateUserDataContainer()->addUserObject(marker); + } + } + + osg::Group* attachTo = merge ? mergeGroup : group; + attachTo->addChild(trans); + ++numinstances; + } + if (numinstances > 0) + { + // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + templateRefs->mObjects.push_back(cnode); + + if (pair.second.mNeedCompile) + { + int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES; + if (!merge) + mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; + stateToCompile._mode = mode; + const_cast(cnode)->accept(stateToCompile); + } + } + } + + if (mergeGroup->getNumChildren()) + { + SceneUtil::Optimizer optimizer; + if (size > 1/8.f) + { + optimizer.setViewPoint(relativeViewPoint); + optimizer.setMergeAlphaBlending(true); + } + optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); + unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; + optimizer.optimize(mergeGroup, options); + + group->addChild(mergeGroup); + + if (mDebugBatches) + { + DebugVisitor dv; + mergeGroup->accept(dv); + } + if (compile) + { + stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; + mergeGroup->accept(stateToCompile); + } + } + + auto ico = mSceneManager->getIncrementalCompileOperation(); + if (!stateToCompile.empty() && ico) + { + auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group); + compileSet->buildCompileMap(ico->getContextSet(), stateToCompile); + ico->add(compileSet, false); + } + + group->getBound(); + group->setNodeMask(Mask_Static); + osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); + if (activeGrid) + { + udc->addUserObject(refnumSet); + group->addCullCallback(new SceneUtil::LightListCallback); + } + udc->addUserObject(templateRefs); + + return group; + } + + unsigned int ObjectPaging::getNodeMask() + { + return Mask_Static; + } + + struct ClearCacheFunctor + { + void operator()(MWRender::ChunkId id, osg::Object* obj) + { + if (intersects(id, mPosition)) + mToClear.insert(id); + } + bool intersects(ChunkId id, osg::Vec3f pos) + { + if (mActiveGridOnly && !std::get<2>(id)) return false; + pos /= ESM::Land::REAL_SIZE; + osg::Vec2f center = std::get<0>(id); + float halfSize = std::get<1>(id)/2; + return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize; + } + osg::Vec3f mPosition; + std::set mToClear; + bool mActiveGridOnly = false; + }; + + bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled) + { + if (!typeFilter(type, false)) + return false; + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false; + if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false; + if (mRefTrackerLocked) return false; + } + + ClearCacheFunctor ccf; + ccf.mPosition = pos; + mCache->call(ccf); + if (ccf.mToClear.empty()) return false; + for (auto chunk : ccf.mToClear) + mCache->removeFromObjectCache(chunk); + return true; + } + + bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos) + { + if (!typeFilter(type, false)) + return false; + + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false; + if (mRefTrackerLocked) return false; + } + + ClearCacheFunctor ccf; + ccf.mPosition = pos; + ccf.mActiveGridOnly = true; + mCache->call(ccf); + if (ccf.mToClear.empty()) return false; + for (auto chunk : ccf.mToClear) + mCache->removeFromObjectCache(chunk); + return true; + } + + + void ObjectPaging::clear() + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + mRefTrackerNew.mDisabled.clear(); + mRefTrackerNew.mBlacklist.clear(); + mRefTrackerLocked = true; + } + + bool ObjectPaging::unlockCache() + { + if (!mRefTrackerLocked) return false; + { + OpenThreads::ScopedLock lock(mRefTrackerMutex); + mRefTrackerLocked = false; + if (mRefTracker == mRefTrackerNew) + return false; + else + mRefTracker = mRefTrackerNew; + } + mCache->clear(); + return true; + } + + struct GetRefnumsFunctor + { + GetRefnumsFunctor(std::set& output) : mOutput(output) {} + void operator()(MWRender::ChunkId chunkId, osg::Object* obj) + { + if (!std::get<2>(chunkId)) return; + const osg::Vec2f& center = std::get<0>(chunkId); + bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); + if (!activeGrid) return; + + osg::UserDataContainer* udc = obj->getUserDataContainer(); + if (udc && udc->getNumUserObjects()) + { + RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); + if (!refnums) return; + mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end()); + } + } + osg::Vec4i mActiveGrid; + std::set& mOutput; + }; + + void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + { + GetRefnumsFunctor grf(out); + grf.mActiveGrid = activeGrid; + mCache->call(grf); + } + + void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); + } + +} diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp new file mode 100644 index 000000000..9c2c54f65 --- /dev/null +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -0,0 +1,92 @@ +#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H +#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H + +#include +#include +#include + +#include + +namespace Resource +{ + class SceneManager; +} +namespace MWWorld +{ + class ESMStore; +} + +namespace MWRender +{ + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + + class ObjectPaging : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + ObjectPaging(Resource::SceneManager* sceneManager); + ~ObjectPaging() = default; + + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile); + + virtual unsigned int getNodeMask() override; + + /// @return true if view needs rebuild + bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled); + + /// @return true if view needs rebuild + bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos); + + void clear(); + + /// Must be called after clear() before rendering starts. + /// @return true if view needs rebuild + bool unlockCache(); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + + private: + Resource::SceneManager* mSceneManager; + bool mActiveGrid; + bool mDebugBatches; + float mMergeFactor; + float mMinSize; + float mMinSizeMergeFactor; + float mMinSizeCostMultiplier; + + OpenThreads::Mutex mRefTrackerMutex; + struct RefTracker + { + std::set mDisabled; + std::set mBlacklist; + bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } + }; + RefTracker mRefTracker; + RefTracker mRefTrackerNew; + bool mRefTrackerLocked; + + const RefTracker& getRefTracker() const { return mRefTracker; } + RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; } + + OpenThreads::Mutex mSizeCacheMutex; + typedef std::map SizeCache; + SizeCache mSizeCache; + }; + + class RefnumMarker : public osg::Object + { + public: + RefnumMarker() : mNumVertices(0) { mRefnum.unset(); } + RefnumMarker(const RefnumMarker ©, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {} + META_Object(MWRender, RefnumMarker) + + ESM::RefNum mRefnum; + unsigned int mNumVertices; + }; +} + +#endif diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index 797794457..c20e81bb2 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" @@ -15,7 +16,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/pathfinding.hpp" -#include "../mwmechanics/coordinateconverter.hpp" #include "vismask.hpp" @@ -105,7 +105,7 @@ void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) if (!pathgrid) return; osg::Vec3f cellPathGridPos(0, 0, 0); - MWMechanics::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); + Misc::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 774b015b5..83c30f8f5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -70,6 +70,8 @@ #include "navmesh.hpp" #include "actorspaths.hpp" #include "recastmesh.hpp" +#include "fogmanager.hpp" +#include "objectpaging.hpp" #ifdef USE_OPENXR #include "../mwvr/vranimation.hpp" @@ -77,15 +79,6 @@ #include "../mwvr/vrenvironment.hpp" #endif -namespace -{ - float DLLandFogStart; - float DLLandFogEnd; - float DLUnderwaterFogStart; - float DLUnderwaterFogEnd; - float DLInteriorFogStart; - float DLInteriorFogEnd; -} namespace MWRender { @@ -211,19 +204,9 @@ namespace MWRender , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) - , mLandFogStart(0.f) - , mLandFogEnd(std::numeric_limits::max()) - , mUnderwaterFogStart(0.f) - , mUnderwaterFogEnd(std::numeric_limits::max()) - , mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor")) - , mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight")) - , mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog")) , mNightEyeFactor(0.f) - , mDistantFog(false) - , mDistantTerrain(false) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) - , mBorders(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); @@ -284,23 +267,12 @@ namespace MWRender { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); - mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); } mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); - DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog"); - DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog"); - DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog"); - DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog"); - DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog"); - DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog"); - - mDistantFog = Settings::Manager::getBool("use distant fog", "Fog"); - mDistantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); - const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); @@ -309,7 +281,7 @@ namespace MWRender mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); - if (mDistantTerrain) + if (Settings::Manager::getBool("distant terrain", "Terrain")) { const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); @@ -322,6 +294,12 @@ namespace MWRender mTerrain.reset(new Terrain::QuadTreeWorld( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + if (Settings::Manager::getBool("object paging", "Terrain")) + { + mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); + static_cast(mTerrain.get())->addChunkManager(mObjectPaging.get()); + mResourceSystem->addResourceManager(mObjectPaging.get()); + } } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); @@ -356,8 +334,9 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); - mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); + mFog.reset(new FogManager()); + mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); mSky->setCamera(mViewer->getCamera()); mSky->setRainIntensityUniform(mWater->getRainIntensityUniform()); @@ -392,6 +371,7 @@ namespace MWRender float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); + updateThirdPersonViewMode(); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); @@ -407,6 +387,19 @@ namespace MWRender mWorkQueue = nullptr; } + void RenderingManager::updateThirdPersonViewMode() + { + if (Settings::Manager::getBool("view over shoulder", "Camera")) + mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::OverShoulder); + else + mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::Standard); + + std::stringstream offset(Settings::Manager::getString("view over shoulder offset", "Camera")); + float horizontal = 30.f, vertical = -10.f; + offset >> horizontal >> vertical; + mCamera->setOverShoulderOffset(horizontal, vertical); + } + osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation() { return mViewer->getIncrementalCompileOperation(); @@ -565,9 +558,9 @@ namespace MWRender bool RenderingManager::toggleBorders() { - mBorders = !mBorders; - mTerrain->setBordersVisible(mBorders); - return mBorders; + bool borders = !mTerrain->getBordersVisible(); + mTerrain->setBordersVisible(borders); + return borders; } bool RenderingManager::toggleRenderMode(RenderMode mode) @@ -613,46 +606,12 @@ namespace MWRender void RenderingManager::configureFog(const ESM::Cell *cell) { - osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); - - if(mDistantFog) - { - float density = std::max(0.2f, cell->mAmbi.mFogDensity); - mLandFogStart = (DLInteriorFogEnd*(1.0f-density) + DLInteriorFogStart*density); - mLandFogEnd = DLInteriorFogEnd; - mUnderwaterFogStart = DLUnderwaterFogStart; - mUnderwaterFogEnd = DLUnderwaterFogEnd; - mFogColor = color; - } - else - configureFog(cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); + mFog->configure(mViewDistance, cell); } void RenderingManager::configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) { - if(mDistantFog) - { - mLandFogStart = dlFactor * (DLLandFogStart - dlOffset*DLLandFogEnd); - mLandFogEnd = dlFactor * (1.0f-dlOffset) * DLLandFogEnd; - mUnderwaterFogStart = DLUnderwaterFogStart; - mUnderwaterFogEnd = DLUnderwaterFogEnd; - } - else - { - if(fogDepth == 0.0) - { - mLandFogStart = 0.0f; - mLandFogEnd = std::numeric_limits::max(); - } - else - { - mLandFogStart = mViewDistance * (1 - fogDepth); - mLandFogEnd = mViewDistance; - } - mUnderwaterFogStart = std::min(mViewDistance, 6666.f) * (1 - underwaterFog); - mUnderwaterFogEnd = std::min(mViewDistance, 6666.f); - } - mFogColor = color; + mFog->configure(mViewDistance, fogDepth, underwaterFog, dlFactor, dlOffset, color); } SkyManager* RenderingManager::getSkyManager() @@ -678,22 +637,14 @@ namespace MWRender mCamera->update(dt, paused); - osg::Vec3f focal, cameraPos; + osg::Vec3d focal, cameraPos; mCamera->getPosition(focal, cameraPos); mCurrentCameraPos = cameraPos; - if (mWater->isUnderwater(cameraPos)) - { - setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); - mStateUpdater->setFogStart(mUnderwaterFogStart); - mStateUpdater->setFogEnd(mUnderwaterFogEnd); - } - else - { - setFogColor(mFogColor); - mStateUpdater->setFogStart(mLandFogStart); - mStateUpdater->setFogEnd(mLandFogEnd); - } + bool isUnderwater = mWater->isUnderwater(cameraPos); + mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); + mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); + setFogColor(mFog->getFogColor(isUnderwater)); } void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) @@ -1058,20 +1009,14 @@ namespace MWRender renderCameraToImage(rttCamera.get(),image,w,h); } - osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) + osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { - if (!ptr.getRefData().getBaseNode()) - return osg::Vec4f(); - - osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(~(Mask_ParticleSystem|Mask_Effect)); - ptr.getRefData().getBaseNode()->accept(computeBoundsVisitor); - + if (!worldbb.valid()) return osg::Vec4f(); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; for (int i=0; i<8; ++i) { - osg::Vec3f corner = computeBoundsVisitor.getBoundingBox().corner(i); + osg::Vec3f corner = worldbb.corner(i); corner = corner * viewProj; float x = (corner.x() + 1.f) * 0.5f; @@ -1097,6 +1042,7 @@ namespace MWRender { RayResult result; result.mHit = false; + result.mHitRefnum.mContentFile = -1; result.mRatio = 0; result.mHitNode = nullptr; if (intersector->containsIntersections()) @@ -1113,6 +1059,7 @@ namespace MWRender result.mHitNode = intersection.nodePath.back(); PtrHolder* ptrHolder = nullptr; + std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) { osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); @@ -1122,11 +1069,25 @@ namespace MWRender { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) ptrHolder = p; + if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) + refnumMarkers.push_back(r); } } if (ptrHolder) result.mHitObject = ptrHolder->mPtr; + + unsigned int vertexCounter = 0; + for (unsigned int i=0; imNumVertices || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) + { + result.mHitRefnum = refnumMarkers[i]->mRefnum; + break; + } + vertexCounter += refnumMarkers[i]->mNumVertices; + } } return result; @@ -1139,6 +1100,7 @@ namespace MWRender mIntersectionVisitor = new osgUtil::IntersectionVisitor; mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); + mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); mIntersectionVisitor->setIntersector(intersector); int mask = ~0; @@ -1222,6 +1184,8 @@ namespace MWRender mSky->setMoonColour(false); notifyWorldSpaceChanged(); + if (mObjectPaging) + mObjectPaging->clear(); } MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) @@ -1381,7 +1345,7 @@ namespace MWRender else if (it->first == "Camera" && it->second == "viewing distance") { mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - if(!mDistantFog) + if(!Settings::Manager::getBool("use distant fog", "Fog")) mStateUpdater->setFogEnd(mViewDistance); updateProjectionMatrix(); } @@ -1419,13 +1383,18 @@ namespace MWRender { if(mCamera->isNearest() && dist > 0.f) mCamera->toggleViewMode(); + else if (override) + mCamera->setBaseCameraDistance(-dist / 120.f * 10, adjust); else - mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override); + mCamera->setCameraDistance(-dist / 120.f * 10, adjust); } else if(mCamera->isFirstPerson() && dist < 0.f) { mCamera->toggleViewMode(); - mCamera->setCameraDistance(0.f, false, override); + if (override) + mCamera->setBaseCameraDistance(0.f, false); + else + mCamera->setCameraDistance(0.f, false); } } @@ -1472,7 +1441,7 @@ namespace MWRender void RenderingManager::changeVanityModeScale(float factor) { if(mCamera->isVanityOrPreviewModeEnabled()) - mCamera->setCameraDistance(-factor/120.f*10, true, true); + mCamera->setBaseCameraDistance(-factor/120.f*10, true); } void RenderingManager::overrideFieldOfView(float val) @@ -1583,4 +1552,43 @@ namespace MWRender mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); } + + void RenderingManager::setActiveGrid(const osg::Vec4i &grid) + { + mTerrain->setActiveGrid(grid); + } + bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) + { + if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) + return false; + if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), enabled)) + { + mTerrain->rebuildViews(); + return true; + } + return false; + } + void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr) + { + if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) + return; + const ESM::RefNum & refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile()) return; + if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3())) + mTerrain->rebuildViews(); + } + bool RenderingManager::pagingUnlockCache() + { + if (mObjectPaging && mObjectPaging->unlockCache()) + { + mTerrain->rebuildViews(); + return true; + } + return false; + } + void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + { + if (mObjectPaging) + mObjectPaging->getPagedRefnums(activeGrid, out); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index bbdadac0d..1e8eab5a9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -42,6 +42,7 @@ namespace osgViewer namespace ESM { struct Cell; + struct RefNum; } namespace Terrain @@ -73,6 +74,7 @@ namespace MWRender class StateUpdater; class EffectManager; + class FogManager; class SkyManager; class NpcAnimation; class Pathgrid; @@ -83,6 +85,7 @@ namespace MWRender class NavMesh; class ActorsPaths; class RecastMesh; + class ObjectPaging; // Result data of ray cast methods. // Needs to be declared outside the RenderingManager class to be forward declarable @@ -94,6 +97,8 @@ namespace MWRender osg::Vec3f mHitPointLocal; MWWorld::Ptr mHitObject; osg::Node* mHitNode; + /// Cast a ray between two points + ESM::RefNum mHitRefnum; float mRatio; }; @@ -161,7 +166,6 @@ namespace MWRender void screenshotFramebuffer(osg::Image* image, int w, int h); // copy directly from framebuffer and scale to given size bool screenshot360(osg::Image* image, std::string settingStr); - /// Cast a ray between two points RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors=false); /// Cast a ray from a node in the scene graph @@ -172,7 +176,7 @@ namespace MWRender RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. - osg::Vec4f getScreenBounds(const MWWorld::Ptr& ptr); + osg::Vec4f getScreenBounds(const osg::BoundingBox &worldbb); void setSkyEnabled(bool enabled); @@ -244,11 +248,19 @@ namespace MWRender void setNavMeshNumber(const std::size_t value); + void setActiveGrid(const osg::Vec4i &grid); + + bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); + void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); + bool pagingUnlockCache(); + void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + private: void updateProjectionMatrix(); void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); + void updateThirdPersonViewMode(); void reportStats() const; @@ -282,7 +294,9 @@ namespace MWRender std::unique_ptr mWater; std::unique_ptr mTerrain; TerrainStorage* mTerrainStorage; + std::unique_ptr mObjectPaging; std::unique_ptr mSky; + std::unique_ptr mFog; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; @@ -292,27 +306,15 @@ namespace MWRender osg::ref_ptr mStateUpdater; - float mLandFogStart; - float mLandFogEnd; - float mUnderwaterFogStart; - float mUnderwaterFogEnd; - osg::Vec4f mUnderwaterColor; - float mUnderwaterWeight; - float mUnderwaterIndoorFog; - osg::Vec4f mFogColor; - osg::Vec4f mAmbientColor; float mNightEyeFactor; float mNearClip; float mViewDistance; - bool mDistantFog : 1; - bool mDistantTerrain : 1; - bool mFieldOfViewOverridden : 1; + bool mFieldOfViewOverridden; float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; - bool mBorders; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 71bef0f5a..ef7a9bf22 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -181,7 +181,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) } void WeaponAnimation::addControllers(const std::map >& nodes, - std::multimap, osg::ref_ptr > &map, osg::Node* objectRoot) + std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { @@ -193,7 +193,7 @@ void WeaponAnimation::addControllers(const std::mapsecond; mSpineControllers[i] = new RotateController(objectRoot); node->addUpdateCallback(mSpineControllers[i]); - map.insert(std::make_pair(node, mSpineControllers[i])); + map.emplace_back(node, mSpineControllers[i]); } } } diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index ece0beaa6..a1988703c 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -41,7 +41,7 @@ namespace MWRender /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. void addControllers(const std::map >& nodes, - std::multimap, osg::ref_ptr >& map, osg::Node* objectRoot); + std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 79639197d..2e426701c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -257,8 +257,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, - ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value); + int modified = ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value; + + ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, modified); + ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified); } }; template @@ -277,6 +279,7 @@ namespace MWScript MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); stat.setModified(value, 0); ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat); + ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); } }; @@ -359,7 +362,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).getAiSequence().getLastRunTypeId(); + const auto value = static_cast(ptr.getClass().getCreatureStats (ptr).getAiSequence().getLastRunTypeId()); runtime.push (value); } @@ -430,13 +433,12 @@ namespace MWScript if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } - else + else if (testedTargetId == "player") // Currently the player ID is hardcoded { - bool turningToPlayer = creatureStats.isTurningToPlayer(); - bool greeting = creatureStats.getGreetingState() == MWMechanics::Greet_InProgress; + MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); + bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress; bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); - if (turningToPlayer || (greeting && sayActive)) - targetsAreEqual = (testedTargetId == "player"); // Currently the player ID is hardcoded + targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); } runtime.push(int(targetsAreEqual)); } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 5a333a5b7..ccc579b30 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -464,5 +464,20 @@ op 0x200030d: RepairedOnMe, explicit op 0x200030e: TestCells op 0x200030f: TestInteriorCells op 0x2000310: ToggleRecastMesh +op 0x2000311: MenuMode +op 0x2000312: Random +op 0x2000313: ScriptRunning +op 0x2000314: StartScript +op 0x2000315: StopScript +op 0x2000316: GetSecondsPassed +op 0x2000317: Enable +op 0x2000318: Disable +op 0x2000319: GetDisabled +op 0x200031a: Enable, explicit +op 0x200031b: Disable, explicit +op 0x200031c: GetDisabled, explicit +op 0x200031d: StartScript, explicit +op 0x200031e: GetDistance +op 0x200031f: GetDistance, explicit -opcodes 0x2000311-0x3ffffff unused +opcodes 0x2000320-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 5dad9d6ec..a7865f0ae 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -24,6 +24,7 @@ namespace { ESM::GlobalScript script; script.mTargetRef.unset(); + script.mRunning = false; if (!ptr.isEmpty()) { if (ptr.getCellRef().hasContentFile()) @@ -42,6 +43,7 @@ namespace ESM::GlobalScript script; script.mTargetId = pair.second; script.mTargetRef = pair.first; + script.mRunning = false; return script; } }; diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 58596a2f4..62febb33d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -192,11 +192,6 @@ namespace MWScript { } - bool InterpreterContext::menuMode() - { - return MWBase::Environment::get().getWindowManager()->isGuiMode(); - } - int InterpreterContext::getGlobalShort (const std::string& name) const { return MWBase::Environment::get().getWorld()->getGlobalInt (name); @@ -415,58 +410,6 @@ namespace MWScript return MWBase::Environment::get().getWorld()->getCellName(); } - bool InterpreterContext::isScriptRunning (const std::string& name) const - { - return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name); - } - - void InterpreterContext::startScript (const std::string& name, const std::string& targetId) - { - MWWorld::Ptr target; - if (targetId.empty()) - target = getReference(false); - else - target = getReferenceImp(targetId); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, target); - } - - void InterpreterContext::stopScript (const std::string& name) - { - MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript (name); - } - - float InterpreterContext::getDistance (const std::string& name, const std::string& id) const - { - // NOTE: id may be empty, indicating an implicit reference - - MWWorld::Ptr ref2 = getReferenceImp(id); - - if (ref2.getContainerStore()) // is the object contained? - { - MWWorld::Ptr container = MWBase::Environment::get().getWorld()->findContainer(ref2); - - if (!container.isEmpty()) - ref2 = container; - else - throw std::runtime_error("failed to find container ptr"); - } - - const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr(name, false); - - // If the objects are in different worldspaces, return a large value (just like vanilla) - if (!ref.isInCell() || !ref2.isInCell() || ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) - return std::numeric_limits::max(); - - double diff[3]; - - const float* const pos1 = ref.getRefData().getPosition().pos; - const float* const pos2 = ref2.getRefData().getPosition().pos; - for (int i=0; i<3; ++i) - diff[i] = pos1[i] - pos2[i]; - - return static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); - } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { std::shared_ptr action = (ptr.getClass().activate(ptr, actor)); @@ -477,29 +420,6 @@ namespace MWScript } } - float InterpreterContext::getSecondsPassed() const - { - return MWBase::Environment::get().getFrameDuration(); - } - - bool InterpreterContext::isDisabled (const std::string& id) const - { - const MWWorld::Ptr ref = getReferenceImp (id, false); - return !ref.getRefData().isEnabled(); - } - - void InterpreterContext::enable (const std::string& id) - { - MWWorld::Ptr ref = getReferenceImp (id, false); - MWBase::Environment::get().getWorld()->enable (ref); - } - - void InterpreterContext::disable (const std::string& id) - { - MWWorld::Ptr ref = getReferenceImp (id, false); - MWBase::Environment::get().getWorld()->disable (ref); - } - int InterpreterContext::getMemberShort (const std::string& id, const std::string& name, bool global) const { diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 1c465ee3e..e7c2790ce 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -77,8 +77,6 @@ namespace MWScript virtual void report (const std::string& message); ///< By default, do nothing. - virtual bool menuMode(); - virtual int getGlobalShort (const std::string& name) const; virtual int getGlobalLong (const std::string& name) const; @@ -121,26 +119,9 @@ namespace MWScript virtual std::string getCurrentCellName() const; - virtual bool isScriptRunning (const std::string& name) const; - - virtual void startScript (const std::string& name, const std::string& targetId = ""); - - virtual void stopScript (const std::string& name); - - virtual float getDistance (const std::string& name, const std::string& id = "") const; - ///< @note if \a id is empty, assumes an implicit reference - void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. - virtual float getSecondsPassed() const; - - virtual bool isDisabled (const std::string& id = "") const; - - virtual void enable (const std::string& id = ""); - - virtual void disable (const std::string& id = ""); - virtual int getMemberShort (const std::string& id, const std::string& name, bool global) const; virtual int getMemberLong (const std::string& id, const std::string& name, bool global) const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b7b91cf5c..0dd0287a7 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include @@ -78,6 +80,117 @@ namespace MWScript { namespace Misc { + class OpMenuMode : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + runtime.push (MWBase::Environment::get().getWindowManager()->isGuiMode()); + } + }; + + class OpRandom : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + Interpreter::Type_Integer limit = runtime[0].mInteger; + runtime.pop(); + + if (limit<0) + throw std::runtime_error ( + "random: argument out of range (Don't be so negative!)"); + + runtime.push (static_cast(::Misc::Rng::rollDice(limit))); // [o, limit) + } + }; + + template + class OpStartScript : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr target = R()(runtime, false); + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, target); + } + }; + + class OpScriptRunning : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + runtime.push(MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name)); + } + }; + + class OpStopScript : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript (name); + } + }; + + class OpGetSecondsPassed : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + runtime.push (MWBase::Environment::get().getFrameDuration()); + } + }; + + template + class OpEnable : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->enable (ptr); + } + }; + + template + class OpDisable : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->disable (ptr); + } + }; + + template + class OpGetDisabled : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (!ptr.getRefData().isEnabled()); + } + }; + class OpPlayBink : public Interpreter::Opcode0 { public: @@ -1456,6 +1569,19 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { + interpreter.installSegment5 (Compiler::Misc::opcodeMenuMode, new OpMenuMode); + interpreter.installSegment5 (Compiler::Misc::opcodeRandom, new OpRandom); + interpreter.installSegment5 (Compiler::Misc::opcodeScriptRunning, new OpScriptRunning); + interpreter.installSegment5 (Compiler::Misc::opcodeStartScript, new OpStartScript); + interpreter.installSegment5 (Compiler::Misc::opcodeStartScriptExplicit, new OpStartScript); + interpreter.installSegment5 (Compiler::Misc::opcodeStopScript, new OpStopScript); + interpreter.installSegment5 (Compiler::Misc::opcodeGetSecondsPassed, new OpGetSecondsPassed); + interpreter.installSegment5 (Compiler::Misc::opcodeEnable, new OpEnable); + interpreter.installSegment5 (Compiler::Misc::opcodeEnableExplicit, new OpEnable); + interpreter.installSegment5 (Compiler::Misc::opcodeDisable, new OpDisable); + interpreter.installSegment5 (Compiler::Misc::opcodeDisableExplicit, new OpDisable); + interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabled, new OpGetDisabled); + interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabledExplicit, new OpGetDisabled); interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); interpreter.installSegment5 (Compiler::Misc::opcodeOnActivateExplicit, new OpOnActivate); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 45eafa960..37751c6d4 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -95,7 +95,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = + Interpreter::Type_Float value = ptr.getClass() .getCreatureStats (ptr) .getAttribute(mIndex) @@ -118,7 +118,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; + Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); @@ -140,7 +140,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; + Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass() @@ -155,9 +155,9 @@ namespace MWScript return; if (value < 0) - attribute.setBase(std::max(0, attribute.getBase() + value)); + attribute.setBase(std::max(0.f, attribute.getBase() + value)); else - attribute.setBase(std::min(100, attribute.getBase() + value)); + attribute.setBase(std::min(100.f, attribute.getBase() + value)); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } @@ -345,7 +345,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getSkill(ptr, mIndex); + Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mIndex); runtime.push (value); } @@ -364,7 +364,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; + Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); @@ -386,7 +386,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; + Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::SkillValue &skill = ptr.getClass() @@ -396,14 +396,14 @@ namespace MWScript if (value == 0) return; - if (((skill.getBase() <= 0) && (value < 0)) - || ((skill.getBase() >= 100) && (value > 0))) + if (((skill.getBase() <= 0.f) && (value < 0.f)) + || ((skill.getBase() >= 100.f) && (value > 0.f))) return; if (value < 0) - skill.setBase(std::max(0, skill.getBase() + value)); + skill.setBase(std::max(0.f, skill.getBase() + value)); else - skill.setBase(std::min(100, skill.getBase() + value)); + skill.setBase(std::min(100.f, skill.getBase() + value)); } }; @@ -499,6 +499,7 @@ namespace MWScript creatureStats.getSpells().purgeEffect(effect.first.mId); } + MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id); creatureStats.getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index d28d9c373..791d054f3 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -39,6 +39,63 @@ namespace MWScript } } + template + class OpGetDistance : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr from = R()(runtime); + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + if (from.getContainerStore()) // is the object contained? + { + MWWorld::Ptr container = MWBase::Environment::get().getWorld()->findContainer(from); + + if (!container.isEmpty()) + from = container; + else + { + std::string error = "Failed to find the container of object '" + from.getCellRef().getRefId() + "'"; + runtime.getContext().report(error); + Log(Debug::Error) << error; + runtime.push(0.f); + return; + } + } + + const MWWorld::Ptr to = MWBase::Environment::get().getWorld()->searchPtr(name, false); + if (to.isEmpty()) + { + std::string error = "Failed to find an instance of object '" + name + "'"; + runtime.getContext().report(error); + Log(Debug::Error) << error; + runtime.push(0.f); + return; + } + + float distance; + // If the objects are in different worldspaces, return a large value (just like vanilla) + if (!to.isInCell() || !from.isInCell() || to.getCell()->getCell()->getCellId().mWorldspace != from.getCell()->getCell()->getCellId().mWorldspace) + distance = std::numeric_limits::max(); + else + { + double diff[3]; + + const float* const pos1 = to.getRefData().getPosition().pos; + const float* const pos2 = from.getRefData().getPosition().pos; + for (int i=0; i<3; ++i) + diff[i] = pos1[i] - pos2[i]; + + distance = static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); + } + + runtime.push(distance); + } + }; + template class OpSetScale : public Interpreter::Opcode0 { @@ -730,6 +787,8 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { + interpreter.installSegment5(Compiler::Transformation::opcodeGetDistance, new OpGetDistance); + interpreter.installSegment5(Compiler::Transformation::opcodeGetDistanceExplicit, new OpGetDistance); interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); interpreter.installSegment5(Compiler::Transformation::opcodeSetScaleExplicit,new OpSetScale); interpreter.installSegment5(Compiler::Transformation::opcodeSetAngle,new OpSetAngle); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index eff1cf0fd..162c176e6 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -147,10 +147,11 @@ namespace MWSound volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); min = sound->mData.mMinRange; max = sound->mData.mMaxRange; - if (min == 0) + if (min == 0 && max == 0) + { min = fAudioDefaultMinDistance; - if (max == 0) max = fAudioDefaultMaxDistance; + } min *= fAudioMinDistanceMult; max *= fAudioMaxDistanceMult; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 86a26212f..9974b8f16 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -213,11 +213,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mPlayerClassId = classId; profile.mPlayerCell = world.getCellName(); - - profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); - profile.mInGameTime.mDay = world.getDay(); - profile.mInGameTime.mMonth = world.getMonth(); - profile.mInGameTime.mYear = world.getYear(); + profile.mInGameTime = world.getEpochTimeStamp(); profile.mTimePlayed = mTimePlayed; profile.mDescription = description; @@ -240,12 +236,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot ESM::ESMWriter writer; - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); - - for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); - ++iter) - writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 + for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles()) + writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0 writer.setFormat (ESM::SavedGame::sCurrentFormat); @@ -346,10 +338,10 @@ void MWState::StateManager::quickSave (std::string name) if (currentCharacter) { - for (Character::SlotIterator it = currentCharacter->begin(); it != currentCharacter->end(); ++it) + for (auto& save : *currentCharacter) { //Visiting slots allows the quicksave finder to find the oldest quicksave - saveFinder.visitSave(&*it); + saveFinder.visitSave(&save); } } @@ -360,12 +352,10 @@ void MWState::StateManager::quickSave (std::string name) void MWState::StateManager::loadGame(const std::string& filepath) { - for (CharacterIterator it = mCharacterManager.begin(); it != mCharacterManager.end(); ++it) + for (const auto& character : mCharacterManager) { - const MWState::Character& character = *it; - for (MWState::Character::SlotIterator slotIt = character.begin(); slotIt != character.end(); ++slotIt) + for (const auto& slot : character) { - const MWState::Slot& slot = *slotIt; if (slot.mPath == boost::filesystem::path(filepath)) { loadGame(&character, slot.mPath.string()); @@ -460,6 +450,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_ENAB: case ESM::REC_LEVC: case ESM::REC_LEVI: + case ESM::REC_CREA: MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap); break; @@ -630,13 +621,12 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const { const std::vector& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); bool notFound = false; - for (std::vector::const_iterator it = profile.mContentFiles.begin(); - it != profile.mContentFiles.end(); ++it) + for (const std::string& contentFile : profile.mContentFiles) { - if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), *it) + if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile) == selectedContentFiles.end()) { - Log(Debug::Warning) << "Warning: Saved game dependency " << *it << " is missing."; + Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; notFound = true; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 9a96e9806..bee6a957d 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -65,23 +66,7 @@ namespace MWWorld mTerrainView = mTerrain->createView(); ListModelsVisitor visitor (mMeshes); - if (cell->getState() == MWWorld::CellStore::State_Loaded) - { - cell->forEach(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 (const std::string& id : objectIds) - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id); - std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); - if (!model.empty()) - mMeshes.push_back(model); - } - } + cell->forEach(visitor); } virtual void abort() @@ -97,7 +82,7 @@ namespace MWWorld try { mTerrain->cacheCell(mTerrainView.get(), mX, mY); - mPreloadedObjects.push_back(mLandManager->getLand(mX, mY)); + mPreloadedObjects.insert(mLandManager->getLand(mX, mY)); } catch(std::exception& e) { @@ -113,17 +98,7 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - if (mPreloadInstances) - { - mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh)); - mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh)); - } - else - { - mPreloadedObjects.push_back(mSceneManager->getTemplate(mesh)); - mPreloadedObjects.push_back(mBulletShapeManager->getShape(mesh)); - } - + bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { @@ -134,11 +109,23 @@ namespace MWWorld if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); - mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); + if (mSceneManager->getVFS()->exists(kfname)) + { + mPreloadedObjects.insert(mKeyframeManager->get(kfname)); + animated = true; + } } - } } + if (mPreloadInstances && animated) + mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); + else + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); + if (mPreloadInstances) + mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); + else + mPreloadedObjects.insert(mBulletShapeManager->getShape(mesh)); + } catch (std::exception& e) { @@ -166,24 +153,28 @@ namespace MWWorld osg::ref_ptr mTerrainView; // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state - std::vector > mPreloadedObjects; + std::set > mPreloadedObjects; }; class TerrainPreloadItem : public SceneUtil::WorkItem { public: - TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) + TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) : mAbort(false) + , mProgress(views.size()) + , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) { } - void storeViews(double referenceTime) + bool storeViews(double referenceTime) { for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime); + if (!mWorld->storeView(mTerrainViews[i], referenceTime)) + return false; + return true; } virtual void doWork() @@ -191,7 +182,7 @@ namespace MWWorld for (unsigned int i=0; ireset(); - mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange); } } @@ -200,11 +191,16 @@ namespace MWWorld mAbort = true; } + int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } + int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } + private: std::atomic mAbort; + std::vector> mProgress; + int mProgressRange; std::vector > mTerrainViews; Terrain::World* mWorld; - std::vector mPreloadPositions; + std::vector mPreloadPositions; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -237,6 +233,7 @@ namespace MWWorld , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) + , mStoreViewsFailCount(0) { } @@ -328,9 +325,6 @@ namespace MWWorld } mPreloadCells.erase(found); - - if (cell->isExterior() && mTerrainPreloadItem && mTerrainPreloadItem->isDone()) - mTerrainPreloadItem->storeViews(0.0); } } @@ -375,7 +369,17 @@ namespace MWWorld if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) { - mTerrainPreloadItem->storeViews(timestamp); + if (!mTerrainPreloadItem->storeViews(timestamp)) + { + if (++mStoreViewsFailCount > 100) + { + OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; + mStoreViewsFailCount = 0; + } + setTerrainPreloadPositions(std::vector()); + } + else + mStoreViewsFailCount = 0; mTerrainPreloadItem = nullptr; } } @@ -415,11 +419,71 @@ namespace MWWorld mUnrefQueue = unrefQueue; } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) { + if (!mTerrainPreloadItem) + return true; + else if (mTerrainPreloadItem->isDone()) + { + if (mTerrainPreloadItem->storeViews(timestamp)) + { + mTerrainPreloadItem = nullptr; + return true; + } + else + { + setTerrainPreloadPositions(std::vector()); + setTerrainPreloadPositions(positions); + return false; + } + } + else + { + progress = mTerrainPreloadItem->getProgress(); + progressRange = mTerrainPreloadItem->getProgressRange(); + return false; + } + } + + void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) + { + const float resetThreshold = ESM::Land::REAL_SIZE; + for (auto pos : mTerrainPreloadPositions) + if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) + { + mTerrainPreloadItem->abort(); + mTerrainPreloadItem->waitTillDone(); + } + setTerrainPreloadPositions(std::vector()); + } + + bool contains(const std::vector& container, const std::vector& contained) + { + for (auto pos : contained) + { + bool found = false; + for (auto pos2 : container) + { + if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } + + void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + { + if (positions.empty()) + mTerrainPreloadPositions.clear(); + else if (contains(mTerrainPreloadPositions, positions)) return; - else if (positions == mTerrainPreloadPositions) + if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { @@ -436,8 +500,11 @@ namespace MWWorld } mTerrainPreloadPositions = positions; - mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); - mWorkQueue->addWorkItem(mTerrainPreloadItem); + if (!positions.empty()) + { + mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); + mWorkQueue->addWorkItem(mTerrainPreloadItem); + } } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 0501a4f9b..e719f2e60 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace Resource @@ -68,7 +69,11 @@ namespace MWWorld void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - void setTerrainPreloadPositions(const std::vector& positions); + typedef std::pair PositionCellGrid; + void setTerrainPreloadPositions(const std::vector& positions); + + bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); + void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); private: Resource::ResourceSystem* mResourceSystem; @@ -83,6 +88,7 @@ namespace MWWorld bool mPreloadInstances; double mLastResourceCacheUpdate; + int mStoreViewsFailCount; struct PreloadEntry { @@ -105,7 +111,7 @@ namespace MWWorld PreloadMap mPreloadCells; std::vector > mTerrainViews; - std::vector mTerrainPreloadPositions; + std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; }; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 599f345b8..2cda83e17 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -106,7 +106,7 @@ namespace template void readReferenceCollection (ESM::ESMReader& reader, - MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap) + MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, MWWorld::CellStore* cellstore) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -141,7 +141,18 @@ namespace if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) { // overwrite existing reference + float oldscale = iter->mRef.getScale(); iter->load (state); + const ESM::Position & oldpos = iter->mRef.getPosition(); + const ESM::Position & newpos = iter->mData.getPosition(); + const MWWorld::Ptr ptr(&*iter, cellstore); + if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) + MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]); + if (!iter->mData.isEnabled()) + { + iter->mData.enable(); + MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); + } return; } @@ -154,28 +165,6 @@ namespace ref.load (state); collection.mList.push_back (ref); } - - struct SearchByRefNumVisitor - { - MWWorld::LiveCellRefBase* mFound; - ESM::RefNum mRefNumToFind; - - SearchByRefNumVisitor(const ESM::RefNum& toFind) - : mFound(nullptr) - , mRefNumToFind(toFind) - { - } - - bool operator()(const MWWorld::Ptr& ptr) - { - if (ptr.getCellRef().getRefNum() == mRefNumToFind) - { - mFound = ptr.getBase(); - return false; - } - return true; - } - }; } namespace MWWorld @@ -252,9 +241,7 @@ namespace MWWorld throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); // Ensure that the object actually exists in the cell - SearchByRefNumVisitor searchVisitor(object.getCellRef().getRefNum()); - forEach(searchVisitor); - if (!searchVisitor.mFound) + if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty()) throw std::runtime_error("moveTo: object is not in this cell"); @@ -809,107 +796,107 @@ namespace MWWorld { case ESM::REC_ACTI: - readReferenceCollection (reader, mActivators, cref, contentFileMap); + readReferenceCollection (reader, mActivators, cref, contentFileMap, this); break; case ESM::REC_ALCH: - readReferenceCollection (reader, mPotions, cref, contentFileMap); + readReferenceCollection (reader, mPotions, cref, contentFileMap, this); break; case ESM::REC_APPA: - readReferenceCollection (reader, mAppas, cref, contentFileMap); + readReferenceCollection (reader, mAppas, cref, contentFileMap, this); break; case ESM::REC_ARMO: - readReferenceCollection (reader, mArmors, cref, contentFileMap); + readReferenceCollection (reader, mArmors, cref, contentFileMap, this); break; case ESM::REC_BOOK: - readReferenceCollection (reader, mBooks, cref, contentFileMap); + readReferenceCollection (reader, mBooks, cref, contentFileMap, this); break; case ESM::REC_CLOT: - readReferenceCollection (reader, mClothes, cref, contentFileMap); + readReferenceCollection (reader, mClothes, cref, contentFileMap, this); break; case ESM::REC_CONT: - readReferenceCollection (reader, mContainers, cref, contentFileMap); + readReferenceCollection (reader, mContainers, cref, contentFileMap, this); break; case ESM::REC_CREA: - readReferenceCollection (reader, mCreatures, cref, contentFileMap); + readReferenceCollection (reader, mCreatures, cref, contentFileMap, this); break; case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, cref, contentFileMap); + readReferenceCollection (reader, mDoors, cref, contentFileMap, this); break; case ESM::REC_INGR: - readReferenceCollection (reader, mIngreds, cref, contentFileMap); + readReferenceCollection (reader, mIngreds, cref, contentFileMap, this); break; case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, cref, contentFileMap); + readReferenceCollection (reader, mCreatureLists, cref, contentFileMap, this); break; case ESM::REC_LEVI: - readReferenceCollection (reader, mItemLists, cref, contentFileMap); + readReferenceCollection (reader, mItemLists, cref, contentFileMap, this); break; case ESM::REC_LIGH: - readReferenceCollection (reader, mLights, cref, contentFileMap); + readReferenceCollection (reader, mLights, cref, contentFileMap, this); break; case ESM::REC_LOCK: - readReferenceCollection (reader, mLockpicks, cref, contentFileMap); + readReferenceCollection (reader, mLockpicks, cref, contentFileMap, this); break; case ESM::REC_MISC: - readReferenceCollection (reader, mMiscItems, cref, contentFileMap); + readReferenceCollection (reader, mMiscItems, cref, contentFileMap, this); break; case ESM::REC_NPC_: - readReferenceCollection (reader, mNpcs, cref, contentFileMap); + readReferenceCollection (reader, mNpcs, cref, contentFileMap, this); break; case ESM::REC_PROB: - readReferenceCollection (reader, mProbes, cref, contentFileMap); + readReferenceCollection (reader, mProbes, cref, contentFileMap, this); break; case ESM::REC_REPA: - readReferenceCollection (reader, mRepairs, cref, contentFileMap); + readReferenceCollection (reader, mRepairs, cref, contentFileMap, this); break; case ESM::REC_STAT: - readReferenceCollection (reader, mStatics, cref, contentFileMap); + readReferenceCollection (reader, mStatics, cref, contentFileMap, this); break; case ESM::REC_WEAP: - readReferenceCollection (reader, mWeapons, cref, contentFileMap); + readReferenceCollection (reader, mWeapons, cref, contentFileMap, this); break; case ESM::REC_BODY: - readReferenceCollection (reader, mBodyParts, cref, contentFileMap); + readReferenceCollection (reader, mBodyParts, cref, contentFileMap, this); break; default: @@ -931,26 +918,22 @@ namespace MWWorld movedTo.load(reader); // Search for the reference. It might no longer exist if its content file was removed. - SearchByRefNumVisitor visitor(refnum); - forEachInternal(visitor); - - if (!visitor.mFound) + Ptr movedRef = searchViaRefNum(refnum); + if (movedRef.isEmpty()) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; continue; } - MWWorld::LiveCellRefBase* movedRef = visitor.mFound; - CellStore* otherCell = callback->getCellStore(movedTo); if (otherCell == nullptr) { - Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() + Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId() << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. // Restore original coordinates: - movedRef->mData.setPosition(movedRef->mRef.getPosition()); + movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition()); continue; } @@ -961,7 +944,7 @@ namespace MWWorld continue; } - moveTo(MWWorld::Ptr(movedRef, this), otherCell); + moveTo(movedRef, otherCell); } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 77b44270e..733e10085 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -421,7 +421,7 @@ namespace MWWorld return canSwim(ptr) || canWalk(ptr) || canFly(ptr); } - int Class::getSkill(const MWWorld::Ptr& ptr, int skill) const + float Class::getSkill(const MWWorld::Ptr& ptr, int skill) const { throw std::runtime_error("class does not support skills"); } @@ -516,4 +516,24 @@ namespace MWWorld result.z() = magicEffect->mData.mBlue / 255.f; return result; } + + void Class::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + { + throw std::runtime_error ("class does not have creature stats"); + } + + float Class::getWalkSpeed(const Ptr& /*ptr*/) const + { + return 0; + } + + float Class::getRunSpeed(const Ptr& /*ptr*/) const + { + return 0; + } + + float Class::getSwimSpeed(const Ptr& /*ptr*/) const + { + return 0; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 687286280..23bf97fc2 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -10,6 +10,7 @@ #include "ptr.hpp" #include "doorstate.hpp" +#include "../mwmechanics/creaturestats.hpp" namespace ESM { @@ -28,7 +29,6 @@ namespace MWPhysics namespace MWMechanics { - class CreatureStats; class NpcStats; struct Movement; } @@ -324,7 +324,7 @@ namespace MWWorld bool isPureLandCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; - virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; + virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const; virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; @@ -363,6 +363,14 @@ namespace MWWorld virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; + + virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + + virtual float getWalkSpeed(const Ptr& ptr) const; + + virtual float getRunSpeed(const Ptr& ptr) const; + + virtual float getSwimSpeed(const Ptr& ptr) const; }; } diff --git a/apps/openmw/mwworld/datetimemanager.cpp b/apps/openmw/mwworld/datetimemanager.cpp new file mode 100644 index 000000000..0894c974d --- /dev/null +++ b/apps/openmw/mwworld/datetimemanager.cpp @@ -0,0 +1,227 @@ +#include "datetimemanager.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "esmstore.hpp" +#include "globals.hpp" +#include "timestamp.hpp" + +namespace +{ + static int getDaysPerMonth(int month) + { + switch (month) + { + case 0: return 31; + case 1: return 28; + case 2: return 31; + case 3: return 30; + case 4: return 31; + case 5: return 30; + case 6: return 31; + case 7: return 31; + case 8: return 30; + case 9: return 31; + case 10: return 30; + case 11: return 31; + } + + throw std::runtime_error ("month out of range"); + } +} + +namespace MWWorld +{ + void DateTimeManager::setup(Globals& globalVariables) + { + mGameHour = globalVariables["gamehour"].getFloat(); + mDaysPassed = globalVariables["dayspassed"].getInteger(); + mDay = globalVariables["day"].getInteger(); + mMonth = globalVariables["month"].getInteger(); + mYear = globalVariables["year"].getInteger(); + mTimeScale = globalVariables["timescale"].getFloat(); + } + + void DateTimeManager::setHour(double hour) + { + if (hour < 0) + hour = 0; + + int days = static_cast(hour / 24); + hour = std::fmod(hour, 24); + mGameHour = static_cast(hour); + + if (days > 0) + setDay(days + mDay); + } + + void DateTimeManager::setDay(int day) + { + if (day < 1) + day = 1; + + int month = mMonth; + while (true) + { + int days = getDaysPerMonth(month); + if (day <= days) + break; + + if (month < 11) + { + ++month; + } + else + { + month = 0; + mYear++; + } + + day -= days; + } + + mDay = day; + mMonth = month; + } + + TimeStamp DateTimeManager::getTimeStamp() const + { + return TimeStamp(mGameHour, mDaysPassed); + } + + float DateTimeManager::getTimeScaleFactor() const + { + return mTimeScale; + } + + ESM::EpochTimeStamp DateTimeManager::getEpochTimeStamp() const + { + ESM::EpochTimeStamp timeStamp; + timeStamp.mGameHour = mGameHour; + timeStamp.mDay = mDay; + timeStamp.mMonth = mMonth; + timeStamp.mYear = mYear; + return timeStamp; + } + + void DateTimeManager::setMonth(int month) + { + if (month < 0) + month = 0; + + int years = month / 12; + month = month % 12; + + int days = getDaysPerMonth(month); + if (mDay > days) + mDay = days; + + mMonth = month; + + if (years > 0) + mYear += years; + } + + void DateTimeManager::advanceTime(double hours, Globals& globalVariables) + { + hours += mGameHour; + setHour(hours); + + int days = static_cast(hours / 24); + if (days > 0) + mDaysPassed += days; + + globalVariables["gamehour"].setFloat(mGameHour); + globalVariables["dayspassed"].setInteger(mDaysPassed); + globalVariables["day"].setInteger(mDay); + globalVariables["month"].setInteger(mMonth); + globalVariables["year"].setInteger(mYear); + } + + std::string DateTimeManager::getMonthName(int month) const + { + if (month == -1) + month = mMonth; + + const int months = 12; + if (month < 0 || month >= months) + return std::string(); + + static const char *monthNames[months] = + { + "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", + "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", + "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" + }; + + const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().get().find(monthNames[month]); + return setting->mValue.getString(); + } + + bool DateTimeManager::updateGlobalFloat(const std::string& name, float value) + { + if (name=="gamehour") + { + setHour(value); + return true; + } + else if (name=="day") + { + setDay(static_cast(value)); + return true; + } + else if (name=="month") + { + setMonth(static_cast(value)); + return true; + } + else if (name=="year") + { + mYear = static_cast(value); + } + else if (name=="timescale") + { + mTimeScale = value; + } + else if (name=="dayspassed") + { + mDaysPassed = static_cast(value); + } + + return false; + } + + bool DateTimeManager::updateGlobalInt(const std::string& name, int value) + { + if (name=="gamehour") + { + setHour(static_cast(value)); + return true; + } + else if (name=="day") + { + setDay(value); + return true; + } + else if (name=="month") + { + setMonth(value); + return true; + } + else if (name=="year") + { + mYear = value; + } + else if (name=="timescale") + { + mTimeScale = static_cast(value); + } + else if (name=="dayspassed") + { + mDaysPassed = value; + } + + return false; + } +} diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp new file mode 100644 index 000000000..b460be746 --- /dev/null +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -0,0 +1,43 @@ +#ifndef GAME_MWWORLD_DATETIMEMANAGER_H +#define GAME_MWWORLD_DATETIMEMANAGER_H + +#include + +namespace ESM +{ + struct EpochTimeStamp; +} + +namespace MWWorld +{ + class Globals; + class TimeStamp; + + class DateTimeManager + { + int mDaysPassed = 0; + int mDay = 0; + int mMonth = 0; + int mYear = 0; + float mGameHour = 0.f; + float mTimeScale = 0.f; + + void setHour(double hour); + void setDay(int day); + void setMonth(int month); + + public: + std::string getMonthName(int month) const; + TimeStamp getTimeStamp() const; + ESM::EpochTimeStamp getEpochTimeStamp() const; + float getTimeScaleFactor() const; + + void advanceTime(double hours, Globals& globalVariables); + + void setup(Globals& globalVariables); + bool updateGlobalInt(const std::string& name, int value); + bool updateGlobalFloat(const std::string& name, float value); + }; +} + +#endif diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1f6ed5102..278b8532e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -136,6 +136,10 @@ void ESMStore::setUp(bool validateRecords) mIds[*record] = storeIt->first; } } + + if (mStaticIds.empty()) + mStaticIds = mIds; + mSkills.setUp(); mMagicEffects.setUp(); mAttributes.setUp(); @@ -273,7 +277,8 @@ void ESMStore::validate() +mSpells.getDynamicSize() +mWeapons.getDynamicSize() +mCreatureLists.getDynamicSize() - +mItemLists.getDynamicSize(); + +mItemLists.getDynamicSize() + +mCreatures.getDynamicSize(); } void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -295,6 +300,7 @@ void ESMStore::validate() mNpcs.write (writer, progress); mItemLists.write (writer, progress); mCreatureLists.write (writer, progress); + mCreatures.write (writer, progress); } bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type) @@ -312,24 +318,8 @@ void ESMStore::validate() case ESM::REC_NPC_: case ESM::REC_LEVI: case ESM::REC_LEVC: - - { - mStores[type]->read (reader); - } - - if (type==ESM::REC_NPC_) - { - // NPC record will always be last and we know that there can be only one - // dynamic NPC record (player) -> We are done here with dynamic record loading - setUp(); - - const ESM::NPC *player = mNpcs.find ("player"); - - if (!mRaces.find (player->mRace) || - !mClasses.find (player->mClass)) - throw std::runtime_error ("Invalid player record (race or class unavailable"); - } - + case ESM::REC_CREA: + mStores[type]->read (reader); return true; case ESM::REC_DYNA: @@ -343,4 +333,15 @@ void ESMStore::validate() } } + void ESMStore::checkPlayer() + { + setUp(); + + const ESM::NPC *player = mNpcs.find ("player"); + + if (!mRaces.find (player->mRace) || + !mClasses.find (player->mClass)) + throw std::runtime_error ("Invalid player record (race or class unavailable"); + } + } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d170a32c5..24364bfb1 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -68,6 +68,8 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; + std::map mStaticIds; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -99,6 +101,14 @@ namespace MWWorld } return it->second; } + int findStatic(const std::string &id) const + { + std::map::const_iterator it = mStaticIds.find(id); + if (it == mStaticIds.end()) { + return 0; + } + return it->second; + } ESMStore() : mDynamicCount(0) @@ -239,6 +249,9 @@ namespace MWWorld bool readRecord (ESM::ESMReader& reader, uint32_t type); ///< \return Known type? + + // To be called when we are done with dynamic record loading + void checkPlayer(); }; template <> diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 69ec5563b..8a481334e 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -2,10 +2,9 @@ #include -#include - #include #include +#include #include "esmstore.hpp" diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d4358532c..ada211470 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -284,12 +284,12 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots // rate weapon for (int i = 0; i < static_cast(weaponSkillsLength); ++i) { - int max = 0; + float max = 0; int maxWeaponSkill = -1; for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { - int skillValue = actor.getClass().getSkill(actor, static_cast(weaponSkills[j])); + float skillValue = actor.getClass().getSkill(actor, static_cast(weaponSkills[j])); if (skillValue > max && !weaponSkillVisited[j]) { max = skillValue; @@ -399,7 +399,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); + float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter) @@ -572,7 +572,8 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) mMagicEffects = MWMechanics::MagicEffects(); - if (actor.getClass().getCreatureStats(actor).isDead()) + const auto& stats = actor.getClass().getCreatureStats(actor); + if (stats.isDead() && stats.isDeathAnimationFinished()) return; for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) @@ -921,16 +922,16 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito } } -void MWWorld::InventoryStore::purgeEffect(short effectId) +void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) { for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) { if (*it != end()) - purgeEffect(effectId, (*it)->getCellRef().getRefId()); + purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); } } -void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId) +void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell) { TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) @@ -963,6 +964,12 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou if (effectIt->mEffectID != effectId) continue; + if (wholeSpell) + { + mPermanentMagicEffectMagnitudes.erase(sourceId); + return; + } + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; magnitude *= params[i].mMultiplier; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index e18132f4b..d597e5f30 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -203,10 +203,10 @@ namespace MWWorld void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); - void purgeEffect (short effectId); + void purgeEffect (short effectId, bool wholeSpell = false); ///< Remove a magic effect - void purgeEffect (short effectId, const std::string& sourceId); + void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false); ///< Remove a magic effect virtual void clear(); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7b4e4302d..1ee6ebe1f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -373,8 +373,6 @@ namespace MWWorld else player.mHasMark = false; - player.mAutoMove = mAutoMove ? 1 : 0; - for (int i=0; i::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) { + // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. + MWWorld::Ptr caster = it->getCaster(); + if (!caster.isEmpty() && caster.getClass().isActor()) + { + if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) + { + cleanupMagicBolt(*it); + it = mMagicBolts.erase(it); + continue; + } + } + osg::Quat orient = it->mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->mValue.getFloat(); @@ -419,8 +431,6 @@ namespace MWWorld update(*it, duration); - MWWorld::Ptr caster = it->getCaster(); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a366f9a75..42fe1a960 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -86,8 +87,21 @@ namespace ); } + std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) + { + bool useAnim = ptr.getClass().useAnim(); + std::string model = ptr.getClass().getModel(ptr); + if (useAnim) + model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); + + const std::string &id = ptr.getCellRef().getRefId(); + if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") + model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + return model; + } + void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -96,15 +110,13 @@ namespace } bool useAnim = ptr.getClass().useAnim(); - std::string model = ptr.getClass().getModel(ptr); - if (useAnim) - model = Misc::ResourceHelpers::correctActorModelPath(model, rendering.getResourceSystem()->getVFS()); + std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - std::string id = ptr.getCellRef().getRefId(); - if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") - model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player - - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, RotationOrder::direct); ptr.getClass().insertObject (ptr, model, physics); @@ -183,27 +195,6 @@ namespace } } - void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, RotationOrder order) - { - setNodeRotation(ptr, rendering, order); - physics.updateRotation(ptr); - } - - void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) - { - if (ptr.getRefData().getBaseNode() != nullptr) - { - float scale = ptr.getCellRef().getScale(); - osg::Vec3f scaleVec (scale, scale, scale); - ptr.getClass().adjustScale(ptr, scaleVec, true); - rendering.scaleObject(ptr, scaleVec); - - physics.updateScale(ptr); - } - } - struct InsertVisitor { MWWorld::CellStore& mCell; @@ -276,50 +267,48 @@ namespace namespace MWWorld { - void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) + void Scene::removeFromPagedRefs(const Ptr &ptr) { - ::updateObjectRotation(ptr, *mPhysics, mRendering, order); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (refnum.hasContentFile() && mPagedRefs.erase(refnum)) + { + if (!ptr.getRefData().getBaseNode()) return; + ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); + setNodeRotation(ptr, mRendering, RotationOrder::direct); + reloadTerrain(); + } } - void Scene::updateObjectScale(const Ptr &ptr) + void Scene::updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics) { - ::updateObjectScale(ptr, *mPhysics, mRendering); + mRendering.moveObject(ptr, pos); + if (movePhysics) + { + mPhysics->updatePosition(ptr); + } } - void Scene::getGridCenter(int &cellX, int &cellY) + void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - int maxX = std::numeric_limits::min(); - int maxY = std::numeric_limits::min(); - int minX = std::numeric_limits::max(); - int minY = std::numeric_limits::max(); - CellStoreCollection::iterator iter = mActiveCells.begin(); - while (iter!=mActiveCells.end()) - { - assert ((*iter)->getCell()->isExterior()); - int x = (*iter)->getCell()->getGridX(); - int y = (*iter)->getCell()->getGridY(); - maxX = std::max(x, maxX); - maxY = std::max(y, maxY); - minX = std::min(x, minX); - minY = std::min(y, minY); - ++iter; - } - cellX = (minX + maxX) / 2; - cellY = (minY + maxY) / 2; + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); + } + + void Scene::updateObjectScale(const Ptr &ptr) + { + float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec (scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + mRendering.scaleObject(ptr, scaleVec); + mPhysics->updateScale(ptr); } void Scene::update (float duration, bool paused) { - mPreloadTimer += duration; - if (mPreloadTimer > 0.1f) - { - preloadCells(0.1f); - mPreloadTimer = 0.f; - } + mPreloader->updateCache(mRendering.getReferenceTime()); + preloadCells(duration); mRendering.update (duration, paused); - - mPreloader->updateCache(mRendering.getReferenceTime()); } void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) @@ -365,6 +354,9 @@ namespace MWWorld if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); + if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) + navigator->removePathgrid(*pathgrid); + const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); @@ -393,7 +385,8 @@ namespace MWWorld float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); @@ -419,6 +412,9 @@ namespace MWWorld heightField->getCollisionObject()->getWorldTransform()); } + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); + // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); @@ -481,6 +477,27 @@ namespace MWWorld mPreloader->clear(); } + osg::Vec4i Scene::gridCenterToBounds(const osg::Vec2i& centerCell) const + { + return osg::Vec4i(centerCell.x()-mHalfGridSize,centerCell.y()-mHalfGridSize,centerCell.x()+mHalfGridSize+1,centerCell.y()+mHalfGridSize+1); + } + + osg::Vec2i Scene::getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i* currentGridCenter) const + { + if (currentGridCenter) + { + float centerX, centerY; + MWBase::Environment::get().getWorld()->indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true); + float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); + const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold + if (distance <= maxDistance) + return *currentGridCenter; + } + osg::Vec2i newCenter; + MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y()); + return newCenter; + } + void Scene::playerMoved(const osg::Vec3f &pos) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); @@ -490,30 +507,13 @@ namespace MWWorld if (!mCurrentCell || !mCurrentCell->isExterior()) return; - // figure out the center of the current cell grid (*not* necessarily mCurrentCell, which is the cell the player is in) - int cellX, cellY; - getGridCenter(cellX, cellY); - float centerX, centerY; - MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); - const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold - float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); - if (distance > maxDistance) - { - int newX, newY; - MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY); - changeCellGrid(newX, newY); - } + osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); + if (newCell != mCurrentGridCenter) + changeCellGrid(pos, newCell.x(), newCell.y()); } - void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent) + void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(loadingListener); - - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); - std::string loadingExteriorText = "#{sLoadingMessage3}"; - loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); - CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { @@ -530,6 +530,14 @@ namespace MWWorld unloadCell (active++); } + mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); + osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); + mRendering.setActiveGrid(newGrid); + + preloadTerrain(pos, true); + mPagedRefs.clear(); + mRendering.getPagedRefnums(newGrid, mPagedRefs); + std::size_t refsToLoad = 0; std::vector> cellsPositionsToLoad; // get the number of refs to load @@ -558,6 +566,11 @@ namespace MWWorld } } + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); + std::string loadingExteriorText = "#{sLoadingMessage3}"; + loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); loadingListener->setProgressRange(refsToLoad); const auto getDistanceToPlayerCell = [&] (const std::pair& cellPosition) @@ -738,14 +751,12 @@ namespace MWWorld MWBase::Environment::get().getWorld()->adjustSky(); - mLastPlayerPos = pos.asVec3(); + mLastPlayerPos = player.getRefData().getPosition().asVec3(); } Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) - , mPreloadTimer(0.f) - , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) @@ -821,6 +832,7 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); // Load cell. + mPagedRefs.clear(); loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -850,7 +862,7 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); - changeCellGrid(x, y, changeEvent); + changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); @@ -873,7 +885,7 @@ namespace MWWorld { InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order @@ -885,7 +897,7 @@ namespace MWWorld { try { - addObject(ptr, *mPhysics, mRendering); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); @@ -917,6 +929,7 @@ namespace MWWorld mRendering.removeObject (ptr); if (ptr.getClass().isActor()) mRendering.removeWaterRippleEmitter(ptr); + ptr.getRefData().setBaseNode(nullptr); } bool Scene::isCellActive(const CellStore &cell) @@ -976,7 +989,8 @@ namespace MWWorld void Scene::preloadCells(float dt) { - std::vector exteriorPositions; + if (dt<=1e-06) return; + std::vector exteriorPositions; const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -984,7 +998,7 @@ namespace MWWorld osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; if (mCurrentCell->isExterior()) - exteriorPositions.push_back(predictedPos); + exteriorPositions.emplace_back(predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter))); mLastPlayerPos = playerPos; @@ -1001,7 +1015,7 @@ namespace MWWorld mPreloader->setTerrainPreloadPositions(exteriorPositions); } - void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) + void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) { std::vector teleportDoors; for (const MWWorld::CellStore* cellStore : mActiveCells) @@ -1035,7 +1049,7 @@ namespace MWWorld int x,y; MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); - exteriorPositions.push_back(pos); + exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } catch (std::exception& e) @@ -1055,7 +1069,7 @@ namespace MWWorld int cellX,cellY; - getGridCenter(cellX,cellY); + cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y(); float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); @@ -1101,11 +1115,41 @@ namespace MWWorld mPreloader->preload(cell, mRendering.getReferenceTime()); } - void Scene::preloadTerrain(const osg::Vec3f &pos) + void Scene::preloadTerrain(const osg::Vec3f &pos, bool sync) { - std::vector vec; - vec.push_back(pos); + std::vector vec; + vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); + if (sync && mRendering.pagingUnlockCache()) + mPreloader->abortTerrainPreloadExcept(nullptr); + else + mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); + if (!sync) return; + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + int progress = 0, initialProgress = -1, progressRange = 0; + while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime())) + { + if (initialProgress == -1) + { + loadingListener->setLabel("#{sLoadingMessage4}"); + initialProgress = progress; + } + if (progress) + { + loadingListener->setProgressRange(std::max(0, progressRange-initialProgress)); + loadingListener->setProgress(progress-initialProgress); + } + else + loadingListener->setProgress(0); + OpenThreads::Thread::microSleep(5000); + } + } + + void Scene::reloadTerrain() + { + mPreloader->setTerrainPreloadPositions(std::vector()); } struct ListFastTravelDestinationsVisitor @@ -1138,7 +1182,7 @@ namespace MWWorld std::vector mList; }; - void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time + void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time { const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); @@ -1159,7 +1203,7 @@ namespace MWWorld int x,y; MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); - exteriorPositions.push_back(pos); + exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index da795f84b..a70d3ccdd 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,6 +1,9 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H +#include +#include + #include "ptr.hpp" #include "globals.hpp" @@ -8,6 +11,8 @@ #include #include +#include + namespace osg { class Vec3f; @@ -72,8 +77,6 @@ namespace MWWorld MWRender::RenderingManager& mRendering; DetourNavigator::Navigator& mNavigator; std::unique_ptr mPreloader; - float mPreloadTimer; - int mHalfGridSize; float mCellLoadingThreshold; float mPreloadDistance; bool mPreloadEnabled; @@ -83,19 +86,27 @@ namespace MWWorld bool mPreloadFastTravel; float mPredictionTime; + static const int mHalfGridSize = Constants::CellGridRadius; + osg::Vec3f mLastPlayerPos; + std::set mPagedRefs; + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); + osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true); + void changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent = true); - void getGridCenter(int& cellX, int& cellY); + typedef std::pair PositionCellGrid; void preloadCells(float dt); - void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); - void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); + + osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; + osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; public: @@ -105,7 +116,8 @@ namespace MWWorld ~Scene(); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); - void preloadTerrain(const osg::Vec3f& pos); + void preloadTerrain(const osg::Vec3f& pos, bool sync=false); + void reloadTerrain(); void unloadCell (CellStoreCollection::iterator iter, bool test = false); @@ -143,8 +155,11 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. + void removeFromPagedRefs(const Ptr &ptr); + void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); + void updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics); bool isCellActive(const CellStore &cell); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a414585ef..4c4417587 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -139,18 +139,26 @@ namespace MWWorld std::string idLower = Misc::StringUtils::lowerCase(id); typename Dynamic::const_iterator dit = mDynamic.find(idLower); - if (dit != mDynamic.end()) { + if (dit != mDynamic.end()) return &dit->second; - } typename std::map::const_iterator it = mStatic.find(idLower); + if (it != mStatic.end()) + return &(it->second); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return 0; + } + template + const T *Store::searchStatic(const std::string &id) const + { + std::string idLower = Misc::StringUtils::lowerCase(id); + typename std::map::const_iterator it = mStatic.find(idLower); + if (it != mStatic.end()) return &(it->second); - } return 0; } + template bool Store::isDynamic(const std::string &id) const { @@ -276,7 +284,7 @@ namespace MWWorld typename std::map::iterator it = mStatic.find(idLower); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + if (it != mStatic.end()) { // delete from the static part of mShared typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); @@ -553,7 +561,7 @@ namespace MWWorld std::map::const_iterator it = mInt.find(cell.mName); - if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + if (it != mInt.end()) { return &(it->second); } @@ -582,6 +590,18 @@ namespace MWWorld return 0; } + const ESM::Cell *Store::searchStatic(int x, int y) const + { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + return 0; + } const ESM::Cell *Store::searchOrCreate(int x, int y) { std::pair key(x, y); @@ -1104,9 +1124,8 @@ namespace MWWorld { auto it = mStatic.find(Misc::StringUtils::lowerCase(id)); - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + if (it != mStatic.end()) mStatic.erase(it); - } return true; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 64c01589e..f119fa928 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -167,6 +167,7 @@ namespace MWWorld void setUp(); const T *search(const std::string &id) const; + const T *searchStatic(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? @@ -297,6 +298,7 @@ namespace MWWorld const ESM::Cell *search(const std::string &id) const; const ESM::Cell *search(int x, int y) const; + const ESM::Cell *searchStatic(int x, int y) const; const ESM::Cell *searchOrCreate(int x, int y); const ESM::Cell *find(const std::string &id) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 84cdd03fa..800ccb57f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -61,6 +61,7 @@ #include "../mwphysics/object.hpp" #include "../mwphysics/constants.hpp" +#include "datetimemanager.hpp" #include "player.hpp" #include "manualref.hpp" #include "cellstore.hpp" @@ -128,33 +129,11 @@ namespace MWWorld LoadersContainer mLoaders; }; - int World::getDaysPerMonth (int month) const - { - switch (month) - { - case 0: return 31; - case 1: return 28; - case 2: return 31; - case 3: return 30; - case 4: return 31; - case 5: return 30; - case 6: return 31; - case 7: return 31; - case 8: return 30; - case 9: return 31; - case 10: return 30; - case 11: return 31; - } - - throw std::runtime_error ("month out of range"); - } - void World::adjustSky() { if (mSky && (isCellExterior() || isCellQuasiExterior())) { - mRendering->skySetDate (mDay->getInteger(), mMonth->getInteger()); - + updateSkyDate(); mRendering->setSkyEnabled(true); } else @@ -171,10 +150,11 @@ namespace MWWorld const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) : mResourceSystem(resourceSystem), mLocalScripts (mStore), - mSky (true), mCells (mStore, mEsm), - mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), + mCells (mStore, mEsm), mSky (true), + mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), + mUserDataPath(userDataPath), mShouldUpdateNavigator(false), mActivationDistanceOverride (activationDistanceOverride), - mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true), + mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { @@ -199,6 +179,8 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); + mCurrentDate.reset(new DateTimeManager()); + fillGlobalVariables(); mStore.setUp(true); @@ -233,13 +215,7 @@ namespace MWWorld void World::fillGlobalVariables() { mGlobalVariables.fill (mStore); - - mGameHour = &mGlobalVariables["gamehour"]; - mDaysPassed = &mGlobalVariables["dayspassed"]; - mDay = &mGlobalVariables["day"]; - mMonth = &mGlobalVariables["month"]; - mYear = &mGlobalVariables["year"]; - mTimeScale = &mGlobalVariables["timescale"]; + mCurrentDate->setup(mGlobalVariables); } void World::startNewGame (bool bypass) @@ -274,7 +250,6 @@ namespace MWWorld if (bypass && !mStartCell.empty()) { ESM::Position pos; - if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos, true); @@ -316,6 +291,7 @@ namespace MWWorld mPhysics->toggleCollisionMode(); MWBase::Environment::get().getWindowManager()->updatePlayer(); + mCurrentDate->setup(mGlobalVariables); } void World::clear() @@ -410,12 +386,13 @@ namespace MWWorld reader.getHNT(mLevitationEnabled, "LEVT"); return; case ESM::REC_PLAY: + mStore.checkPlayer(); mPlayer->readRecord(reader, type); if (getPlayerPtr().isInCell()) { - mWorldScene->preloadCell(getPlayerPtr().getCell(), true); if (getPlayerPtr().getCell()->isExterior()) mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); + mWorldScene->preloadCell(getPlayerPtr().getCell(), true); } break; default: @@ -646,26 +623,20 @@ namespace MWWorld void World::setGlobalInt (const std::string& name, int value) { - if (name=="gamehour") - setHour (value); - else if (name=="day") - setDay (value); - else if (name=="month") - setMonth (value); - else - mGlobalVariables[name].setInteger (value); + bool dateUpdated = mCurrentDate->updateGlobalInt(name, value); + if (dateUpdated) + updateSkyDate(); + + mGlobalVariables[name].setInteger (value); } void World::setGlobalFloat (const std::string& name, float value) { - if (name=="gamehour") - setHour (value); - else if (name=="day") - setDay(static_cast(value)); - else if (name=="month") - setMonth(static_cast(value)); - else - mGlobalVariables[name].setFloat (value); + bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value); + if (dateUpdated) + updateSkyDate(); + + mGlobalVariables[name].setFloat(value); } int World::getGlobalInt (const std::string& name) const @@ -683,6 +654,11 @@ namespace MWWorld return mGlobalVariables.getType (name); } + std::string World::getMonthName (int month) const + { + return mCurrentDate->getMonthName(month); + } + std::string World::getCellName (const MWWorld::CellStore *cell) const { if (!cell) @@ -750,7 +726,10 @@ namespace MWWorld Ptr ret = searchPtr(name, activeOnly); if (!ret.isEmpty()) return ret; - throw std::runtime_error ("unknown ID: " + name); + std::string error = "failed to find an instance of object '" + name + "'"; + if (activeOnly) + error += " in active cells"; + throw std::runtime_error(error); } Ptr World::searchPtrViaActorId (int actorId) @@ -843,6 +822,13 @@ namespace MWWorld if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) mWorldScene->addObjectToScene (reference); + + if (reference.getCellRef().getRefNum().hasContentFile()) + { + int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); + if (mRendering->pagingEnableObject(type, reference, true)) + mWorldScene->reloadTerrain(); + } } } @@ -867,20 +853,27 @@ namespace MWWorld void World::disable (const Ptr& reference) { + if (!reference.getRefData().isEnabled()) + return; + // disable is a no-op for items in containers if (!reference.isInCell()) return; - if (reference.getRefData().isEnabled()) - { - if (reference == getPlayerPtr()) - throw std::runtime_error("can not disable player object"); + if (reference == getPlayerPtr()) + throw std::runtime_error("can not disable player object"); - reference.getRefData().disable(); + reference.getRefData().disable(); - if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) - mWorldScene->removeObjectFromScene (reference); + if (reference.getCellRef().getRefNum().hasContentFile()) + { + int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); + if (mRendering->pagingEnableObject(type, reference, false)) + mWorldScene->reloadTerrain(); } + + if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) + mWorldScene->removeObjectFromScene (reference); } void World::advanceTime (double hours, bool incremental) @@ -898,130 +891,29 @@ namespace MWWorld } mWeatherManager->advanceTime (hours, incremental); + mCurrentDate->advanceTime(hours, mGlobalVariables); + updateSkyDate(); if (!incremental) { mRendering->notifyWorldSpaceChanged(); mProjectileManager->clear(); } - - hours += mGameHour->getFloat(); - - setHour (hours); - - int days = static_cast(hours / 24); - - if (days>0) - mDaysPassed->setInteger ( - days + mDaysPassed->getInteger()); - } - - void World::setHour (double hour) - { - if (hour<0) - hour = 0; - - int days = static_cast(hour / 24); - - hour = std::fmod (hour, 24); - - mGameHour->setFloat(static_cast(hour)); - - if (days>0) - setDay (days + mDay->getInteger()); - } - - void World::setDay (int day) - { - if (day<1) - day = 1; - - int month = mMonth->getInteger(); - - while (true) - { - int days = getDaysPerMonth (month); - if (day<=days) - break; - - if (month<11) - { - ++month; - } - else - { - month = 0; - mYear->setInteger(mYear->getInteger()+1); - } - - day -= days; - } - - mDay->setInteger(day); - mMonth->setInteger(month); - - mRendering->skySetDate(day, month); - } - - void World::setMonth (int month) - { - if (month<0) - month = 0; - - int years = month / 12; - month = month % 12; - - int days = getDaysPerMonth (month); - - if (mDay->getInteger()>days) - mDay->setInteger (days); - - mMonth->setInteger (month); - - if (years>0) - mYear->setInteger (years+mYear->getInteger()); - - mRendering->skySetDate (mDay->getInteger(), month); - } - - int World::getDay() const - { - return mDay->getInteger(); - } - - int World::getMonth() const - { - return mMonth->getInteger(); } - int World::getYear() const + float World::getTimeScaleFactor() const { - return mYear->getInteger(); + return mCurrentDate->getTimeScaleFactor(); } - std::string World::getMonthName (int month) const + TimeStamp World::getTimeStamp() const { - if (month==-1) - month = getMonth(); - - const int months = 12; - - if (month<0 || month>=months) - return ""; - - static const char *monthNames[months] = - { - "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", - "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", - "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" - }; - - return mStore.get().find (monthNames[month])->mValue.getString(); + return mCurrentDate->getTimeStamp(); } - TimeStamp World::getTimeStamp() const + ESM::EpochTimeStamp World::getEpochTimeStamp() const { - return TimeStamp (mGameHour->getFloat(), mDaysPassed->getInteger()); + return mCurrentDate->getEpochTimeStamp(); } bool World::toggleSky() @@ -1046,11 +938,6 @@ namespace MWWorld mRendering->skySetMoonColour (red); } - float World::getTimeScaleFactor() const - { - return mTimeScale->getFloat(); - } - void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); @@ -1105,6 +992,8 @@ namespace MWWorld changeToExteriorCell (position, adjustPlayerPos, changeEvent); else changeToInteriorCell (cellId.mWorldspace, position, adjustPlayerPos, changeEvent); + + mCurrentDate->setup(mGlobalVariables); } void World::markCellAsUnchanged() @@ -1356,20 +1245,22 @@ namespace MWWorld } if (haveToMove && newPtr.getRefData().getBaseNode()) { - mRendering->moveObject(newPtr, vec); + mWorldScene->updateObjectPosition(newPtr, vec, movePhysics); if (movePhysics) { - mPhysics->updatePosition(newPtr); - mPhysics->updatePtr(ptr, newPtr); - - if (const auto object = mPhysics->getObject(newPtr)) + if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); } } + if (isPlayer) - { mWorldScene->playerMoved(vec); + else + { + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(newPtr); } + return newPtr; } @@ -1398,9 +1289,15 @@ namespace MWWorld if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); - ptr.getCellRef().setScale(scale); + if (scale != ptr.getCellRef().getScale()) + { + ptr.getCellRef().setScale(scale); + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); + } - mWorldScene->updateObjectScale(ptr); + if(ptr.getRefData().getBaseNode() != 0) + mWorldScene->updateObjectScale(ptr); if (mPhysics->getActor(ptr)) mNavigator->addAgent(getPathfindingHalfExtents(ptr)); @@ -1444,6 +1341,9 @@ namespace MWWorld ptr.getRefData().setPosition(pos); + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); + if(ptr.getRefData().getBaseNode() != 0) { const auto order = flags & MWBase::RotationFlag_inverseOrder @@ -1724,7 +1624,7 @@ namespace MWWorld if(ptr != getPlayerPtr() ) { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once + if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(door),ptr); } @@ -1853,6 +1753,16 @@ namespace MWWorld return mStore.overrideRecord(record); } + const ESM::Creature *World::createOverrideRecord(const ESM::Creature &record) + { + return mStore.overrideRecord(record); + } + + const ESM::NPC *World::createOverrideRecord(const ESM::NPC &record) + { + return mStore.overrideRecord(record); + } + const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; @@ -1988,15 +1898,35 @@ namespace MWWorld int nightEye = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude()); mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); - mRendering->getCamera()->setCameraDistance(); + auto* camera = mRendering->getCamera(); + camera->setCameraDistance(); if(!mRendering->getCamera()->isFirstPerson()) { - osg::Vec3f focal, camera; - mRendering->getCamera()->getPosition(focal, camera); - float radius = mRendering->getNearClipDistance()*2.5f; - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, camera, radius); + float cameraObstacleLimit = mRendering->getNearClipDistance() * 2.5f; + float focalObstacleLimit = std::max(cameraObstacleLimit, 10.0f); + + // Adjust focal point. + osg::Vec3d focal = camera->getFocalPoint(); + osg::Vec3d focalOffset = camera->getFocalPointOffset(); + float offsetLen = focalOffset.length(); + if (offsetLen > 0) + { + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal - focalOffset, focal, focalObstacleLimit); + if (result.mHit) + { + double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; + if (adjustmentCoef < -1) + adjustmentCoef = -1; + camera->adjustFocalPoint(focalOffset * adjustmentCoef); + } + } + + // Adjust camera position. + osg::Vec3d cameraPos; + camera->getPosition(focal, cameraPos); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, cameraPos, cameraObstacleLimit); if (result.mHit) - mRendering->getCamera()->setCameraDistance((result.mHitPos - focal).length() - radius, false, false); + mRendering->getCamera()->setCameraDistance((result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(), false); } } @@ -2065,8 +1995,15 @@ namespace MWWorld // retrieve object dimensions so we know where to place the floating label if (!object.isEmpty ()) { - osg::Vec4f screenBounds = mRendering->getScreenBounds(object); - + osg::BoundingBox bb = mPhysics->getBoundingBox(object); + if (!bb.valid() && object.getRefData().getBaseNode()) + { + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + object.getRefData().getBaseNode()->accept(computeBoundsVisitor); + bb = computeBoundsVisitor.getBoundingBox(); + } + osg::Vec4f screenBounds = mRendering->getScreenBounds(bb); MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); } @@ -2096,6 +2033,14 @@ namespace MWWorld rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer); facedObject = rayToObject.mHitObject; + if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile()) + { + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum); + if (!facedObject.isEmpty()) break; + } + } if (rayToObject.mHit) mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist; else @@ -3104,9 +3049,9 @@ namespace MWWorld { if (actor != MWMechanics::getPlayer()) { - for (const MWMechanics::AiPackage* package : stats.getAiSequence()) + for (const auto& package : stats.getAiSequence()) { - if (package->getTypeId() == MWMechanics::AiPackage::TypeIdCast) + if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { target = package->getTarget(); break; @@ -3725,6 +3670,8 @@ namespace MWWorld std::string World::exportSceneGraph(const Ptr &ptr) { std::string file = mUserDataPath + "/openmw.osgt"; + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); mRendering->exportSceneGraph(ptr, file, "Ascii"); return file; } @@ -4100,4 +4047,16 @@ namespace MWWorld { return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); } + + void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mNavigator->reportStats(frameNumber, stats); + mPhysics->reportStats(frameNumber, stats); + } + + void World::updateSkyDate() + { + ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp(); + mRendering->skySetDate(currentDate.mDay, currentDate.mMonth); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b78543ef0..4d7e5163c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -22,6 +22,7 @@ namespace osg { class Group; + class Stats; } namespace osgViewer @@ -70,6 +71,7 @@ namespace MWPhysics namespace MWWorld { + class DateTimeManager; class WeatherManager; class Player; class ProjectileManager; @@ -78,20 +80,13 @@ namespace MWWorld class World final: public MWBase::World { + private: Resource::ResourceSystem* mResourceSystem; std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; - bool mSky; - - ESM::Variant* mGameHour; - ESM::Variant* mDaysPassed; - ESM::Variant* mDay; - ESM::Variant* mMonth; - ESM::Variant* mYear; - ESM::Variant* mTimeScale; Cells mCells; @@ -103,8 +98,10 @@ namespace MWWorld std::unique_ptr mRendering; std::unique_ptr mWorldScene; std::unique_ptr mWeatherManager; + std::unique_ptr mCurrentDate; std::shared_ptr mProjectileManager; + bool mSky; bool mGodMode; bool mScriptsEnabled; std::vector mContentFiles; @@ -112,21 +109,33 @@ namespace MWWorld std::string mUserDataPath; osg::Vec3f mDefaultHalfExtents; - bool mShouldUpdateNavigator = false; - - // not implemented - World (const World&); - World& operator= (const World&); + bool mShouldUpdateNavigator; int mActivationDistanceOverride; + std::string mStartCell; + + float mSwimHeightScale; + + float mDistanceToFacedObject; + + bool mTeleportEnabled; + bool mLevitationEnabled; + bool mGoToJail; + int mDaysInPrison; + bool mPlayerTraveling; + bool mPlayerInJail; + + float mSpellPreloadTimer; + std::map mDoorStates; ///< only holds doors that are currently moving. 1 = opening, 2 = closing - std::string mStartCell; + // not implemented + World (const World&); + World& operator= (const World&); void updateWeather(float duration, bool paused = false); - int getDaysPerMonth (int month) const; void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); @@ -142,10 +151,6 @@ namespace MWWorld MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); - public: // FIXME - void addContainerScripts(const Ptr& reference, CellStore* cell) override; - void removeContainerScripts(const Ptr& reference) override; - private: void PCDropped (const Ptr& item); bool rotateDoor(const Ptr door, DoorState state, float duration); @@ -164,6 +169,8 @@ namespace MWWorld void fillGlobalVariables(); + void updateSkyDate(); + /** * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) * @param fileCollections- Container which holds content file names and their paths @@ -173,19 +180,6 @@ namespace MWWorld void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader); - float mSwimHeightScale; - - float mDistanceToFacedObject; - - bool mTeleportEnabled; - bool mLevitationEnabled; - bool mGoToJail; - int mDaysInPrison; - bool mPlayerTraveling; - bool mPlayerInJail; - - float mSpellPreloadTimer; - float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); @@ -193,6 +187,9 @@ namespace MWWorld MWWorld::ConstPtr getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ); public: + // FIXME + void addContainerScripts(const Ptr& reference, CellStore* cell) override; + void removeContainerScripts(const Ptr& reference) override; World ( osgViewer::Viewer* viewer, @@ -321,24 +318,14 @@ namespace MWWorld void advanceTime (double hours, bool incremental = false) override; ///< Advance in-game time. - void setHour (double hour) override; - ///< Set in-game time hour. - - void setMonth (int month) override; - ///< Set in-game time month. - - void setDay (int day) override; - ///< Set in-game time day. - - int getDay() const override; - int getMonth() const override; - int getYear() const override; - std::string getMonthName (int month = -1) const override; ///< Return name of month (-1: current month) TimeStamp getTimeStamp() const override; - ///< Return current in-game time stamp. + ///< Return current in-game time and number of day since new game start. + + ESM::EpochTimeStamp getEpochTimeStamp() const override; + ///< Return current in-game date and time. bool toggleSky() override; ///< \return Resulting mode @@ -494,6 +481,14 @@ namespace MWWorld ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record + const ESM::Creature *createOverrideRecord (const ESM::Creature& record) override; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + const ESM::NPC *createOverrideRecord (const ESM::NPC& record) override; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + void update (float duration, bool paused) override; void updatePhysics (float duration, bool paused) override; @@ -754,6 +749,8 @@ namespace MWWorld int getActiveWeaponType(void) override; bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; }; } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 276877508..5a92d5d28 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -37,6 +37,7 @@ namespace std::deque mPath; std::back_insert_iterator> mOut; float mStepSize; + AreaCosts mAreaCosts; DetourNavigatorNavigatorTest() : mPlayerPosition(0, 0, 0) @@ -80,7 +81,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } @@ -88,7 +89,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::StartPolygonNotFound); } @@ -97,7 +98,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::StartPolygonNotFound); } @@ -118,7 +119,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.85963428020477294921875), @@ -168,7 +169,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.85963428020477294921875), @@ -202,7 +203,7 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.87826788425445556640625), @@ -253,7 +254,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.87826788425445556640625), @@ -289,7 +290,7 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.85963428020477294921875), @@ -346,7 +347,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.96328866481781005859375), @@ -402,7 +403,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.9393787384033203125), @@ -455,7 +456,7 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, 185.33331298828125), @@ -501,7 +502,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ @@ -548,7 +549,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_EQ(mPath, std::deque({ @@ -595,7 +596,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 215, -94.75363922119140625), @@ -644,7 +645,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.85963428020477294921875), @@ -739,7 +740,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-215, 215, 1.8782780170440673828125), diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index e34d6278a..92740c65f 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -23,17 +23,23 @@ namespace DetourNavigator namespace { template - struct Wrapper { + struct Wrapper + { const T& mValue; }; template - inline testing::Message& writeRange(testing::Message& message, const Range& range) + inline testing::Message& writeRange(testing::Message& message, const Range& range, std::size_t newLine) { - message << "{\n"; + message << "{"; + std::size_t i = 0; for (const auto& v : range) - message << Wrapper::type> {v} << ",\n"; - return message << "}"; + { + if (i++ % newLine == 0) + message << "\n"; + message << Wrapper::type> {v} << ", "; + } + return message << "\n}"; } } @@ -60,22 +66,34 @@ namespace testing return (*this) << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue; } + template <> + inline testing::Message& Message::operator <<(const Wrapper& value) + { + return (*this) << value.mValue; + } + template <> inline testing::Message& Message::operator <<(const std::deque& value) { - return writeRange(*this, value); + return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { - return writeRange(*this, value); + return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { - return writeRange(*this, value); + return writeRange(*this, value, 3); + } + + template <> + inline testing::Message& Message::operator <<(const std::vector& value) + { + return writeRange(*this, value, 3); } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index c86dee6e5..bcbf448ac 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -104,11 +104,9 @@ namespace -0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, - 0.5, 0, -0.5, - -0.5, 0, 0.5, 0.5, 0, 0.5, })); - EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 2, 1, 3})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } @@ -127,7 +125,7 @@ namespace -1, -2, 1, 1, -2, -1, -1, -2, -1, - })); + })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ 0, 2, 3, 3, 1, 0, @@ -141,7 +139,7 @@ namespace 2, 6, 7, 7, 6, 4, 4, 5, 7, - })); + })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(12, AreaType_ground)); } @@ -166,37 +164,35 @@ namespace ); const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ - 1, 0, -1, - -1, 0, 1, + -1, -2, -1, + -1, -2, 1, -1, 0, -1, - 1, 2, 1, - -1, 2, 1, - 1, 2, -1, + -1, 0, 1, -1, 2, -1, - 1, -2, 1, - -1, -2, 1, + -1, 2, 1, 1, -2, -1, - -1, -2, -1, + 1, -2, 1, 1, 0, -1, - -1, 0, 1, 1, 0, 1, - })); + 1, 2, -1, + 1, 2, 1, + })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ - 0, 1, 2, - 3, 5, 6, - 6, 4, 3, - 3, 7, 9, - 9, 5, 3, - 3, 4, 8, - 8, 7, 3, - 10, 8, 4, - 4, 6, 10, - 10, 6, 5, - 5, 9, 10, - 10, 9, 7, - 7, 8, 10, - 11, 12, 13, - })); + 8, 3, 2, + 11, 10, 4, + 4, 5, 11, + 11, 7, 6, + 6, 10, 11, + 11, 5, 1, + 1, 7, 11, + 0, 1, 5, + 5, 4, 0, + 0, 4, 10, + 10, 6, 0, + 0, 6, 7, + 7, 1, 0, + 8, 3, 9, + })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(14, AreaType_ground)); } @@ -413,4 +409,24 @@ namespace RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} })); } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape_with_duplicated_vertices) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + mesh.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + const auto recastMesh = builder.create(mGeneration, mRevision); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + -1, 0, -1, + -1, 0, 1, + 1, 0, -1, + 1, 0, 1, + })) << recastMesh->getVertices(); + EXPECT_EQ(recastMesh->getIndices(), std::vector({2, 1, 0, 2, 1, 3})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); + } } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 8d97bbcbf..10e06d1ff 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -79,16 +79,9 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) - QT4_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) - QT4_WRAP_UI(UI_HDRS ${WIZARD_UI}) -else() - QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) - QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) - QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) -endif() +QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) +QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) +QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -109,23 +102,12 @@ target_link_libraries(openmw-wizard components ) -if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(openmw-wizard - ${QT_QTGUI_LIBRARY} - ${QT_QTCORE_LIBRARY}) - - if (WIN32) - target_link_libraries(openmw-wizard ${QT_QTMAIN_LIBRARY}) - endif() -else() - target_link_libraries(openmw-wizard Qt5::Widgets Qt5::Core) -endif() +target_link_libraries(openmw-wizard Qt5::Widgets Qt5::Core) if (OPENMW_USE_UNSHIELD) target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES}) endif() - if(DPKG_PROGRAM) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION games COMPONENT openmw-wizard) endif() diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 4aacbaec9..d4a63c676 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -127,25 +127,24 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) QString key(fullKey.at(1)); int index = buffer.lastIndexOf(section); - if (index != -1) { - // Look for the next section - index = buffer.indexOf(QLatin1Char('['), index + 1); - - if (index == -1 ) { - // We are at the last section, append it to the bottom of the file - buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - continue; - } else { - // Not at last section, add the key at the index - buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - } - - } else { + if (index == -1) { // Add the section to the end of the file, because it's not found buffer.append(QString("\n%1\n").arg(section)); - i.previous(); + index = buffer.lastIndexOf(section); + } + + // Look for the next section + index = buffer.indexOf(QLatin1Char('['), index + 1); + + if (index == -1 ) { + // We are at the last section, append it to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // Not at last section, add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); } } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 1353173f4..160c6f721 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -120,9 +120,10 @@ void Wizard::MainWizard::addLogText(const QString &text) QTextStream out(&file); if (!text.isEmpty()) - out << text << endl; - -// file.close(); + { + out << text << "\n"; + out.flush(); + } } void Wizard::MainWizard::setupGameSettings() diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index f385437b4..2408cae2b 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -160,7 +160,7 @@ macro (openmw_add_executable target) if (MSVC) if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) - set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)") + set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$") endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) endif (MSVC) endmacro (openmw_add_executable) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 859c79920..1d4a84ce1 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -111,7 +111,7 @@ add_component_dir (compiler add_component_dir (interpreter context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes - miscopcodes opcodes runtime scriptopcodes spatialopcodes types defines + miscopcodes opcodes runtime types defines ) add_component_dir (translation @@ -200,14 +200,8 @@ if (USE_QT) helpviewer ) - if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) - else() - QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) - endif() + QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -252,13 +246,7 @@ if (WIN32) endif() if (USE_QT) - if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(components - ${QT_QTCORE_LIBRARY} - ${QT_QTGUI_LIBRARY}) - else() - target_link_libraries(components Qt5::Widgets Qt5::Core) - endif() + target_link_libraries(components Qt5::Widgets Qt5::Core) endif() if (GIT_CHECKOUT) @@ -267,9 +255,6 @@ endif (GIT_CHECKOUT) if (WIN32) target_link_libraries(components shlwapi) - if(MINGW) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOGDI") - endif(MINGW) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 @@ -287,5 +272,5 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) if (BULLET_USE_DOUBLES) - add_definitions(-DBT_USE_DOUBLE_PRECISION) + target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) endif() diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 20873385b..8c8fe640e 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -372,9 +372,7 @@ namespace Compiler keyword==Scanner::K_elseif || keyword==Scanner::K_while || keyword==Scanner::K_endwhile || keyword==Scanner::K_return || keyword==Scanner::K_messagebox || keyword==Scanner::K_set || - keyword==Scanner::K_to || keyword==Scanner::K_startscript || - keyword==Scanner::K_stopscript || keyword==Scanner::K_enable || - keyword==Scanner::K_disable) + keyword==Scanner::K_to) { return parseName (loc.mLiteral, loc, scanner); } @@ -385,53 +383,6 @@ namespace Compiler { if (mRefOp && mNextOperand) { - if (keyword==Scanner::K_getdisabled) - { - start(); - - mTokenLoc = loc; - - Generator::getDisabled (mCode, mLiterals, mExplicit); - mOperands.push_back ('l'); - mExplicit.clear(); - mRefOp = false; - - std::vector ignore; - parseArguments ("x", scanner, ignore); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_getdistance) - { - start(); - - mTokenLoc = loc; - parseArguments ("c", scanner); - - Generator::getDistance (mCode, mLiterals, mExplicit); - mOperands.push_back ('f'); - mExplicit.clear(); - mRefOp = false; - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_scriptrunning) - { - start(); - - mTokenLoc = loc; - parseArguments ("c", scanner); - - Generator::scriptRunning (mCode); - mOperands.push_back ('l'); - - mExplicit.clear(); - mRefOp = false; - mNextOperand = false; - return true; - } // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) @@ -483,84 +434,6 @@ namespace Compiler mNextOperand = false; return true; } - else if (keyword==Scanner::K_menumode) - { - start(); - - mTokenLoc = loc; - - Generator::menuMode (mCode); - mOperands.push_back ('l'); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_random) - { - start(); - - mTokenLoc = loc; - parseArguments ("l", scanner); - - Generator::random (mCode); - mOperands.push_back ('f'); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_scriptrunning) - { - start(); - - mTokenLoc = loc; - parseArguments ("c", scanner); - - Generator::scriptRunning (mCode); - mOperands.push_back ('l'); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_getdistance) - { - start(); - - mTokenLoc = loc; - parseArguments ("c", scanner); - - Generator::getDistance (mCode, mLiterals, ""); - mOperands.push_back ('f'); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_getsecondspassed) - { - start(); - - mTokenLoc = loc; - - Generator::getSecondsPassed (mCode); - mOperands.push_back ('f'); - - mNextOperand = false; - return true; - } - else if (keyword==Scanner::K_getdisabled) - { - start(); - - mTokenLoc = loc; - - Generator::getDisabled (mCode, mLiterals, ""); - mOperands.push_back ('l'); - - std::vector ignore; - parseArguments ("x", scanner, ignore); - - mNextOperand = false; - return true; - } else { // check for custom extensions diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index f23d2d86e..3989ef0f4 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -241,6 +241,15 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { + extensions.registerFunction ("menumode", 'l', "", opcodeMenuMode); + extensions.registerFunction ("random", 'f', "l", opcodeRandom); + extensions.registerFunction ("scriptrunning", 'l', "c", opcodeScriptRunning); + extensions.registerInstruction ("startscript", "c", opcodeStartScript, opcodeStartScriptExplicit); + extensions.registerInstruction ("stopscript", "c", opcodeStopScript); + extensions.registerFunction ("getsecondspassed", 'f', "", opcodeGetSecondsPassed); + extensions.registerInstruction ("enable", "x", opcodeEnable, opcodeEnableExplicit); + extensions.registerInstruction ("disable", "x", opcodeDisable, opcodeDisableExplicit); + extensions.registerFunction ("getdisabled", 'l', "x", opcodeGetDisabled, opcodeGetDisabledExplicit); extensions.registerFunction ("xbox", 'l', "", opcodeXBox); extensions.registerFunction ("onactivate", 'l', "", opcodeOnActivate, opcodeOnActivateExplicit); extensions.registerInstruction ("activate", "x", opcodeActivate, opcodeActivateExplicit); @@ -414,13 +423,13 @@ namespace Compiler for (int i=0; isetCurrentIndex(0); } +class AddOnProxyModel : public QSortFilterProxyModel +{ +public: + explicit AddOnProxyModel(QObject* parent = nullptr) : + QSortFilterProxyModel(parent) + {} + + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + { + static const QString ContentTypeAddon = QString::number((int)ContentSelectorModel::ContentType_Addon); + + QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent); + const QString userRole = sourceModel()->data(nameIndex, Qt::UserRole).toString(); + + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent) && userRole == ContentTypeAddon; + } +}; + void ContentSelectorView::ContentSelector::buildAddonView() { ui.addonView->setVisible (true); - mAddonProxyModel = new QSortFilterProxyModel(this); - mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); - mAddonProxyModel->setFilterRole (Qt::UserRole); + mAddonProxyModel = new AddOnProxyModel(this); + mAddonProxyModel->setFilterRegExp(searchFilter()->text()); + mAddonProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mAddonProxyModel->setDynamicSortFilter (true); mAddonProxyModel->setSourceModel (mContentModel); + connect(ui.searchFilter, SIGNAL(textEdited(QString)), mAddonProxyModel, SLOT(setFilterWildcard(QString))); + connect(ui.searchFilter, SIGNAL(textEdited(QString)), this, SLOT(slotSearchFilterTextChanged(QString))); + ui.addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(activated(const QModelIndex&)), this, SLOT(slotAddonTableItemActivated(const QModelIndex&))); @@ -261,3 +282,8 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() clipboard->setText(filepaths); } } + +void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText) +{ + ui.addonView->setDragEnabled(newText.isEmpty()); +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 9c34e24fd..f1058d510 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -48,6 +48,9 @@ namespace ContentSelectorView QToolButton *refreshButton() const { return ui.refreshButton; } + QLineEdit *searchFilter() const + { return ui.searchFilter; } + private: @@ -74,6 +77,7 @@ namespace ContentSelectorView void slotCheckMultiSelectedItems(); void slotUncheckMultiSelectedItems(); void slotCopySelectedItemsPaths(); + void slotSearchFilterTextChanged(const QString& newText); }; } diff --git a/components/detournavigator/areatype.hpp b/components/detournavigator/areatype.hpp index 0daa524ca..9d99421af 100644 --- a/components/detournavigator/areatype.hpp +++ b/components/detournavigator/areatype.hpp @@ -9,8 +9,18 @@ namespace DetourNavigator { AreaType_null = RC_NULL_AREA, AreaType_water, + AreaType_door, + AreaType_pathgrid, AreaType_ground = RC_WALKABLE_AREA, }; + + struct AreaCosts + { + float mWater = 1.0f; + float mDoor = 2.0f; + float mPathgrid = 1.0f; + float mGround = 1.0f; + }; } #endif diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 0f8f2c09a..f1de71207 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -8,6 +8,7 @@ #include "settingsutils.hpp" #include "debug.hpp" #include "status.hpp" +#include "areatype.hpp" #include #include @@ -269,7 +270,7 @@ namespace DetourNavigator template Status findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, - const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const Settings& settings, OutputIterator& out) { dtNavMeshQuery navMeshQuery; @@ -278,6 +279,10 @@ namespace DetourNavigator dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); + queryFilter.setAreaCost(AreaType_water, areaCosts.mWater); + queryFilter.setAreaCost(AreaType_door, areaCosts.mDoor); + queryFilter.setAreaCost(AreaType_pathgrid, areaCosts.mPathgrid); + queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); dtPolyRef startRef = 0; osg::Vec3f startPolygonPosition; diff --git a/components/detournavigator/flags.hpp b/components/detournavigator/flags.hpp index 684d4fbba..887fd4264 100644 --- a/components/detournavigator/flags.hpp +++ b/components/detournavigator/flags.hpp @@ -13,6 +13,7 @@ namespace DetourNavigator Flag_walk = 1 << 0, Flag_swim = 1 << 1, Flag_openDoor = 1 << 2, + Flag_usePathgrid = 1 << 3, }; inline std::ostream& operator <<(std::ostream& stream, const Flag value) @@ -27,6 +28,8 @@ namespace DetourNavigator return stream << "swim"; case Flag_openDoor: return stream << "openDoor"; + case Flag_usePathgrid: + return stream << "usePathgrid"; } return stream; @@ -45,7 +48,7 @@ namespace DetourNavigator else { bool first = true; - for (const auto flag : {Flag_walk, Flag_swim, Flag_openDoor}) + for (const auto flag : {Flag_walk, Flag_swim, Flag_openDoor, Flag_usePathgrid}) { if (value.mValue & flag) { diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index ae26ced7e..beee95113 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -98,6 +98,42 @@ namespace return result; } + Flag getFlag(AreaType areaType) + { + switch (areaType) + { + case AreaType_null: + return Flag_none; + case AreaType_ground: + return Flag_walk; + case AreaType_water: + return Flag_swim; + case AreaType_door: + return Flag_openDoor; + case AreaType_pathgrid: + return Flag_usePathgrid; + } + return Flag_none; + } + + std::vector getOffMeshConAreas(const std::vector& connections) + { + std::vector result; + result.reserve(connections.size()); + std::transform(connections.begin(), connections.end(), std::back_inserter(result), + [] (const OffMeshConnection& v) { return v.mAreaType; }); + return result; + } + + std::vector getOffMeshFlags(const std::vector& connections) + { + std::vector result; + result.reserve(connections.size()); + std::transform(connections.begin(), connections.end(), std::back_inserter(result), + [] (const OffMeshConnection& v) { return getFlag(v.mAreaType); }); + return result; + } + rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) { @@ -334,12 +370,7 @@ namespace void setPolyMeshFlags(rcPolyMesh& polyMesh) { for (int i = 0; i < polyMesh.npolys; ++i) - { - if (polyMesh.areas[i] == AreaType_ground) - polyMesh.flags[i] = Flag_walk; - else if (polyMesh.areas[i] == AreaType_water) - polyMesh.flags[i] = Flag_swim; - } + polyMesh.flags[i] = getFlag(static_cast(polyMesh.areas[i])); } bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, @@ -395,8 +426,8 @@ namespace const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); const std::vector offMeshConDir(offMeshConnections.size(), DT_OFFMESH_CON_BIDIR); - const std::vector offMeshConAreas(offMeshConnections.size(), AreaType_ground); - const std::vector offMeshConFlags(offMeshConnections.size(), Flag_openDoor); + const std::vector offMeshConAreas = getOffMeshConAreas(offMeshConnections); + const std::vector offMeshConFlags = getOffMeshFlags(offMeshConnections); dtNavMeshCreateParams params; params.verts = polyMesh.verts; diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 99f1e258d..3e7f54178 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -9,6 +9,12 @@ #include "recastmesh.hpp" #include "recastmeshtiles.hpp" +namespace ESM +{ + struct Cell; + struct Pathgrid; +} + namespace DetourNavigator { struct ObjectShapes @@ -139,12 +145,21 @@ namespace DetourNavigator */ virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; + virtual void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) = 0; + + virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0; + /** * @brief update start background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ virtual void update(const osg::Vec3f& playerPosition) = 0; + /** + * @brief disable navigator updates + */ + virtual void setUpdatesEnabled(bool enabled) = 0; + /** * @brief wait locks thread until all tiles are updated from last update call. */ @@ -162,7 +177,8 @@ namespace DetourNavigator */ template Status findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, - const osg::Vec3f& end, const Flags includeFlags, OutputIterator& out) const + const osg::Vec3f& end, const Flags includeFlags, const DetourNavigator::AreaCosts& areaCosts, + OutputIterator& out) const { static_assert( std::is_same< @@ -177,7 +193,7 @@ namespace DetourNavigator const auto settings = getSettings(); return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, settings, out); + toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, out); } /** diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 3ecfd8b51..c47cf9766 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -2,6 +2,9 @@ #include "debug.hpp" #include "settingsutils.hpp" +#include +#include + #include namespace DetourNavigator @@ -9,6 +12,7 @@ namespace DetourNavigator NavigatorImpl::NavigatorImpl(const Settings& settings) : mSettings(settings) , mNavMeshManager(mSettings) + , mUpdatesEnabled(true) { } @@ -54,7 +58,8 @@ namespace DetourNavigator mNavMeshManager.addOffMeshConnection( id, toNavMeshCoordinates(mSettings, shapes.mConnectionStart), - toNavMeshCoordinates(mSettings, shapes.mConnectionEnd) + toNavMeshCoordinates(mSettings, shapes.mConnectionEnd), + AreaType_door ); return true; } @@ -95,7 +100,7 @@ namespace DetourNavigator const auto water = mWaterIds.find(id); if (water != mWaterIds.end()) result = mNavMeshManager.removeObject(water->second) || result; - mNavMeshManager.removeOffMeshConnection(id); + mNavMeshManager.removeOffMeshConnections(id); return result; } @@ -111,13 +116,41 @@ namespace DetourNavigator return mNavMeshManager.removeWater(cellPosition); } + void NavigatorImpl::addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) + { + Misc::CoordinateConverter converter(&cell); + for (auto edge : pathgrid.mEdges) + { + const auto src = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV0])); + const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1])); + mNavMeshManager.addOffMeshConnection( + ObjectId(&pathgrid), + toNavMeshCoordinates(mSettings, src), + toNavMeshCoordinates(mSettings, dst), + AreaType_pathgrid + ); + } + } + + void NavigatorImpl::removePathgrid(const ESM::Pathgrid& pathgrid) + { + mNavMeshManager.removeOffMeshConnections(ObjectId(&pathgrid)); + } + void NavigatorImpl::update(const osg::Vec3f& playerPosition) { + if (!mUpdatesEnabled) + return; removeUnusedNavMeshes(); for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } + void NavigatorImpl::setUpdatesEnabled(bool enabled) + { + mUpdatesEnabled = enabled; + } + void NavigatorImpl::wait() { mNavMeshManager.wait(); diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index be291f501..a1d66463c 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -4,6 +4,8 @@ #include "navigator.hpp" #include "navmeshmanager.hpp" +#include + namespace DetourNavigator { class NavigatorImpl final : public Navigator @@ -38,8 +40,14 @@ namespace DetourNavigator bool removeWater(const osg::Vec2i& cellPosition) override; + void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) final; + + void removePathgrid(const ESM::Pathgrid& pathgrid) final; + void update(const osg::Vec3f& playerPosition) override; + void setUpdatesEnabled(bool enabled) override; + void wait() override; SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; @@ -55,6 +63,7 @@ namespace DetourNavigator private: Settings mSettings; NavMeshManager mNavMeshManager; + bool mUpdatesEnabled; std::map mAgents; std::unordered_map mAvoidIds; std::unordered_map mWaterIds; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index ee23e67be..9279e77e3 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -60,8 +60,14 @@ namespace DetourNavigator return false; } + void addPathgrid(const ESM::Cell& /*cell*/, const ESM::Pathgrid& /*pathgrid*/) final {} + + void removePathgrid(const ESM::Pathgrid& /*pathgrid*/) final {} + void update(const osg::Vec3f& /*playerPosition*/) override {} + void setUpdatesEnabled(bool enabled) override {} + void wait() override {} SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index d88d9706a..43d330648 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -110,10 +110,9 @@ namespace DetourNavigator return true; } - void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end) + void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType) { - if (!mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end})) - return; + mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType}); const auto startTilePosition = getTilePosition(mSettings, start); const auto endTilePosition = getTilePosition(mSettings, end); @@ -124,18 +123,11 @@ namespace DetourNavigator addChangedTile(endTilePosition, ChangeType::add); } - void NavMeshManager::removeOffMeshConnection(const ObjectId id) + void NavMeshManager::removeOffMeshConnections(const ObjectId id) { - if (const auto connection = mOffMeshConnectionsManager.remove(id)) - { - const auto startTilePosition = getTilePosition(mSettings, connection->mStart); - const auto endTilePosition = getTilePosition(mSettings, connection->mEnd); - - addChangedTile(startTilePosition, ChangeType::remove); - - if (startTilePosition != endTilePosition) - addChangedTile(endTilePosition, ChangeType::remove); - } + const auto changedTiles = mOffMeshConnectionsManager.remove(id); + for (const auto& tile : changedTiles) + addChangedTile(tile, ChangeType::update); } void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index a6bdca09b..f3861f8f2 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -39,9 +39,9 @@ namespace DetourNavigator bool reset(const osg::Vec3f& agentHalfExtents); - void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end); + void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType); - void removeOffMeshConnection(const ObjectId id); + void removeOffMeshConnections(const ObjectId id); void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index 60e8ecbbb..ca999dbdb 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H +#include "areatype.hpp" + #include namespace DetourNavigator @@ -9,6 +11,7 @@ namespace DetourNavigator { osg::Vec3f mStart; osg::Vec3f mEnd; + AreaType mAreaType; }; } diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 155ce3296..de707f3a8 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -11,14 +11,11 @@ #include -#include - #include #include -#include -#include #include #include +#include namespace DetourNavigator { @@ -29,12 +26,11 @@ namespace DetourNavigator : mSettings(settings) {} - bool add(const ObjectId id, const OffMeshConnection& value) + void add(const ObjectId id, const OffMeshConnection& value) { const auto values = mValues.lock(); - if (!values->mById.insert(std::make_pair(id, value)).second) - return false; + values->mById.insert(std::make_pair(id, value)); const auto startTilePosition = getTilePosition(mSettings, value.mStart); const auto endTilePosition = getTilePosition(mSettings, value.mEnd); @@ -43,32 +39,32 @@ namespace DetourNavigator if (startTilePosition != endTilePosition) values->mByTilePosition[endTilePosition].insert(id); - - return true; } - boost::optional remove(const ObjectId id) + std::set remove(const ObjectId id) { const auto values = mValues.lock(); - const auto itById = values->mById.find(id); + const auto byId = values->mById.equal_range(id); - if (itById == values->mById.end()) - return boost::none; + if (byId.first == byId.second) { + return {}; + } - const auto result = itById->second; + std::set removed; - values->mById.erase(itById); + std::for_each(byId.first, byId.second, [&] (const auto& v) { + const auto startTilePosition = getTilePosition(mSettings, v.second.mStart); + const auto endTilePosition = getTilePosition(mSettings, v.second.mEnd); - const auto startTilePosition = getTilePosition(mSettings, result.mStart); - const auto endTilePosition = getTilePosition(mSettings, result.mEnd); + removed.emplace(startTilePosition); + if (startTilePosition != endTilePosition) + removed.emplace(endTilePosition); + }); - removeByTilePosition(values->mByTilePosition, startTilePosition, id); + values->mById.erase(byId.first, byId.second); - if (startTilePosition != endTilePosition) - removeByTilePosition(values->mByTilePosition, endTilePosition, id); - - return result; + return removed; } std::vector get(const TilePosition& tilePosition) @@ -85,9 +81,8 @@ namespace DetourNavigator std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), [&] (const ObjectId v) { - const auto itById = values->mById.find(v); - if (itById != values->mById.end()) - result.push_back(itById->second); + const auto byId = values->mById.equal_range(v); + std::for_each(byId.first, byId.second, [&] (const auto& v) { result.push_back(v.second); }); }); return result; @@ -96,7 +91,7 @@ namespace DetourNavigator private: struct Values { - std::unordered_map mById; + std::multimap mById; std::map> mByTilePosition; }; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index f61368357..59f60394d 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -17,11 +17,50 @@ #include #include +#include namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; + namespace + { + void optimizeRecastMesh(std::vector& indices, std::vector& vertices) + { + std::vector> uniqueVertices; + uniqueVertices.reserve(vertices.size() / 3); + + for (std::size_t i = 0, n = vertices.size() / 3; i < n; ++i) + uniqueVertices.emplace_back(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]); + + std::sort(uniqueVertices.begin(), uniqueVertices.end()); + const auto end = std::unique(uniqueVertices.begin(), uniqueVertices.end()); + uniqueVertices.erase(end, uniqueVertices.end()); + + if (uniqueVertices.size() == vertices.size() / 3) + return; + + for (std::size_t i = 0, n = indices.size(); i < n; ++i) + { + const auto index = indices[i]; + const auto vertex = std::make_tuple(vertices[index * 3], vertices[index * 3 + 1], vertices[index * 3 + 2]); + const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); + assert(it != uniqueVertices.end()); + assert(*it == vertex); + indices[i] = std::distance(uniqueVertices.begin(), it); + } + + vertices.resize(uniqueVertices.size() * 3); + + for (std::size_t i = 0, n = uniqueVertices.size(); i < n; ++i) + { + vertices[i * 3] = std::get<0>(uniqueVertices[i]); + vertices[i * 3 + 1] = std::get<1>(uniqueVertices[i]); + vertices[i * 3 + 2] = std::get<2>(uniqueVertices[i]); + } + } + } + RecastMeshBuilder::RecastMeshBuilder(const Settings& settings, const TileBounds& bounds) : mSettings(settings) , mBounds(bounds) @@ -112,8 +151,9 @@ namespace DetourNavigator mWater.push_back(RecastMesh::Water {cellSize, transform}); } - std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) const + std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) { + optimizeRecastMesh(mIndices, mVertices); return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index d28558d0f..fc2bbbc02 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator void addWater(const int mCellSize, const btTransform& transform); - std::shared_ptr create(std::size_t generation, std::size_t revision) const; + std::shared_ptr create(std::size_t generation, std::size_t revision); void reset(); diff --git a/components/esm/activespells.cpp b/components/esm/activespells.cpp index 4f51a619e..46558ceb7 100644 --- a/components/esm/activespells.cpp +++ b/components/esm/activespells.cpp @@ -16,7 +16,6 @@ namespace ESM esm.writeHNT ("CAST", params.mCasterActorId); esm.writeHNString ("DISP", params.mDisplayName); - esm.writeHNT ("TIME", params.mTimeStamp); for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) { @@ -25,12 +24,15 @@ namespace ESM esm.writeHNT ("ARG_", effectIt->mArg); esm.writeHNT ("MAGN", effectIt->mMagnitude); esm.writeHNT ("DURA", effectIt->mDuration); + esm.writeHNT ("LEFT", effectIt->mTimeLeft); } } } void ActiveSpells::load(ESMReader &esm) { + int format = esm.getFormat(); + while (esm.isNextSub("ID__")) { std::string spellId = esm.getHString(); @@ -38,7 +40,10 @@ namespace ESM ActiveSpellParams params; esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); - esm.getHNT (params.mTimeStamp, "TIME"); + + // spell casting timestamp, no longer used + if (esm.isNextSub("TIME")) + esm.skipHSub(); while (esm.isNextSub("MGEF")) { @@ -48,6 +53,11 @@ namespace ESM esm.getHNOT(effect.mArg, "ARG_"); esm.getHNT (effect.mMagnitude, "MAGN"); esm.getHNT (effect.mDuration, "DURA"); + if (format < 9) + effect.mTimeLeft = effect.mDuration; + else + esm.getHNT (effect.mTimeLeft, "LEFT"); + params.mEffects.push_back(effect); } mSpells.insert(std::make_pair(spellId, params)); diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp index d9e9a8c63..20b2f652d 100644 --- a/components/esm/activespells.hpp +++ b/components/esm/activespells.hpp @@ -21,6 +21,7 @@ namespace ESM float mMagnitude; int mArg; // skill or attribute float mDuration; + float mTimeLeft; }; // format 0, saved games only @@ -29,7 +30,6 @@ namespace ESM struct ActiveSpellParams { std::vector mEffects; - ESM::TimeStamp mTimeStamp; std::string mDisplayName; int mCasterActorId; }; diff --git a/components/esm/attr.hpp b/components/esm/attr.hpp index c0a54c659..dd891a96d 100644 --- a/components/esm/attr.hpp +++ b/components/esm/attr.hpp @@ -21,7 +21,7 @@ struct Attribute Endurance = 5, Personality = 6, Luck = 7, - Length + Length = 8 }; AttributeID mId; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index ab6ba2754..4b9852d65 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -232,19 +232,3 @@ void ESM::CellRef::blank() mPos.rot[i] = 0; } } - -bool ESM::operator== (const RefNum& left, const RefNum& right) -{ - return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; -} - -bool ESM::operator< (const RefNum& left, const RefNum& right) -{ - if (left.mIndexright.mIndex) - return false; - - return left.mContentFileright.mIndex) + return false; + return left.mContentFile::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + { + esm.writeHNString("CORP", it->first); + + const CorprusStats & stats = it->second; + esm.writeHNT("WORS", stats.mWorsenings); + esm.writeHNT("TIME", stats.mNextWorsening); + } } void ESM::CreatureStats::blank() @@ -245,4 +266,5 @@ void ESM::CreatureStats::blank() mDrawState = 0; mDeathAnimation = -1; mLevel = 1; + mCorprusSpells.clear(); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 8c69553a3..79a576587 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -9,6 +9,7 @@ #include "defs.hpp" +#include "attr.hpp" #include "spellstate.hpp" #include "activespells.hpp" #include "magiceffects.hpp" @@ -22,7 +23,13 @@ namespace ESM // format 0, saved games only struct CreatureStats { - StatState mAttributes[8]; + struct CorprusStats + { + int mWorsenings[Attribute::Length]; + TimeStamp mNextWorsening; + }; + + StatState mAttributes[Attribute::Length]; StatState mDynamic[3]; MagicEffects mMagicEffects; @@ -76,9 +83,9 @@ namespace ESM int mDrawState; signed char mDeathAnimation; ESM::TimeStamp mTimeOfDeath; - int mLevel; + std::map mCorprusSpells; SpellState mSpells; ActiveSpells mActiveSpells; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 0f0478faa..0f9cefab1 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -14,6 +14,14 @@ struct TimeStamp int mDay; }; +struct EpochTimeStamp +{ + float mGameHour; + int mDay; + int mMonth; + int mYear; +}; + // Pixel color value. Standard four-byte rr,gg,bb,aa format. typedef uint32_t Color; @@ -49,6 +57,26 @@ struct Position }; #pragma pack(pop) +bool inline operator== (const Position& left, const Position& right) noexcept +{ + return left.pos[0] == right.pos[0] && + left.pos[1] == right.pos[1] && + left.pos[2] == right.pos[2] && + left.rot[0] == right.rot[0] && + left.rot[1] == right.rot[1] && + left.rot[2] == right.rot[2]; +} + +bool inline operator!= (const Position& left, const Position& right) noexcept +{ + return left.pos[0] != right.pos[0] || + left.pos[1] != right.pos[1] || + left.pos[2] != right.pos[2] || + left.rot[0] != right.rot[0] || + left.rot[1] != right.rot[1] || + left.rot[2] != right.rot[2]; +} + template struct FourCC { @@ -58,51 +86,51 @@ struct FourCC enum RecNameInts { // format 0 / legacy - REC_ACTI = 0x49544341, - REC_ALCH = 0x48434c41, - REC_APPA = 0x41505041, - REC_ARMO = 0x4f4d5241, - REC_BODY = 0x59444f42, - REC_BOOK = 0x4b4f4f42, - REC_BSGN = 0x4e475342, - REC_CELL = 0x4c4c4543, - REC_CLAS = 0x53414c43, - REC_CLOT = 0x544f4c43, - REC_CNTC = 0x43544e43, - REC_CONT = 0x544e4f43, - REC_CREA = 0x41455243, - REC_CREC = 0x43455243, - REC_DIAL = 0x4c414944, - REC_DOOR = 0x524f4f44, - REC_ENCH = 0x48434e45, - REC_FACT = 0x54434146, - REC_GLOB = 0x424f4c47, - REC_GMST = 0x54534d47, - REC_INFO = 0x4f464e49, - REC_INGR = 0x52474e49, - REC_LAND = 0x444e414c, - REC_LEVC = 0x4356454c, - REC_LEVI = 0x4956454c, - REC_LIGH = 0x4847494c, - REC_LOCK = 0x4b434f4c, - REC_LTEX = 0x5845544c, - REC_MGEF = 0x4645474d, - REC_MISC = 0x4353494d, - REC_NPC_ = 0x5f43504e, - REC_NPCC = 0x4343504e, - REC_PGRD = 0x44524750, - REC_PROB = 0x424f5250, - REC_RACE = 0x45434152, - REC_REGN = 0x4e474552, - REC_REPA = 0x41504552, - REC_SCPT = 0x54504353, - REC_SKIL = 0x4c494b53, - REC_SNDG = 0x47444e53, - REC_SOUN = 0x4e554f53, - REC_SPEL = 0x4c455053, - REC_SSCR = 0x52435353, - REC_STAT = 0x54415453, - REC_WEAP = 0x50414557, + REC_ACTI = FourCC<'A','C','T','I'>::value, + REC_ALCH = FourCC<'A','L','C','H'>::value, + REC_APPA = FourCC<'A','P','P','A'>::value, + REC_ARMO = FourCC<'A','R','M','O'>::value, + REC_BODY = FourCC<'B','O','D','Y'>::value, + REC_BOOK = FourCC<'B','O','O','K'>::value, + REC_BSGN = FourCC<'B','S','G','N'>::value, + REC_CELL = FourCC<'C','E','L','L'>::value, + REC_CLAS = FourCC<'C','L','A','S'>::value, + REC_CLOT = FourCC<'C','L','O','T'>::value, + REC_CNTC = FourCC<'C','N','T','C'>::value, + REC_CONT = FourCC<'C','O','N','T'>::value, + REC_CREA = FourCC<'C','R','E','A'>::value, + REC_CREC = FourCC<'C','R','E','C'>::value, + REC_DIAL = FourCC<'D','I','A','L'>::value, + REC_DOOR = FourCC<'D','O','O','R'>::value, + REC_ENCH = FourCC<'E','N','C','H'>::value, + REC_FACT = FourCC<'F','A','C','T'>::value, + REC_GLOB = FourCC<'G','L','O','B'>::value, + REC_GMST = FourCC<'G','M','S','T'>::value, + REC_INFO = FourCC<'I','N','F','O'>::value, + REC_INGR = FourCC<'I','N','G','R'>::value, + REC_LAND = FourCC<'L','A','N','D'>::value, + REC_LEVC = FourCC<'L','E','V','C'>::value, + REC_LEVI = FourCC<'L','E','V','I'>::value, + REC_LIGH = FourCC<'L','I','G','H'>::value, + REC_LOCK = FourCC<'L','O','C','K'>::value, + REC_LTEX = FourCC<'L','T','E','X'>::value, + REC_MGEF = FourCC<'M','G','E','F'>::value, + REC_MISC = FourCC<'M','I','S','C'>::value, + REC_NPC_ = FourCC<'N','P','C','_'>::value, + REC_NPCC = FourCC<'N','P','C','C'>::value, + REC_PGRD = FourCC<'P','G','R','D'>::value, + REC_PROB = FourCC<'P','R','O','B'>::value, + REC_RACE = FourCC<'R','A','C','E'>::value, + REC_REGN = FourCC<'R','E','G','N'>::value, + REC_REPA = FourCC<'R','E','P','A'>::value, + REC_SCPT = FourCC<'S','C','P','T'>::value, + REC_SKIL = FourCC<'S','K','I','L'>::value, + REC_SNDG = FourCC<'S','N','D','G'>::value, + REC_SOUN = FourCC<'S','O','U','N'>::value, + REC_SPEL = FourCC<'S','P','E','L'>::value, + REC_SSCR = FourCC<'S','S','C','R'>::value, + REC_STAT = FourCC<'S','T','A','T'>::value, + REC_WEAP = FourCC<'W','E','A','P'>::value, // format 0 - saved games REC_SAVE = FourCC<'S','A','V','E'>::value, diff --git a/components/esm/doorstate.hpp b/components/esm/doorstate.hpp index 1251b9059..04ad110d6 100644 --- a/components/esm/doorstate.hpp +++ b/components/esm/doorstate.hpp @@ -7,18 +7,18 @@ namespace ESM { // format 0, saved games only - struct DoorState : public ObjectState + struct DoorState final : public ObjectState { int mDoorState = 0; - virtual void load (ESMReader &esm); - virtual void save (ESMWriter &esm, bool inInventory = false) const; + void load (ESMReader &esm) final; + void save (ESMWriter &esm, bool inInventory = false) const final; - virtual DoorState& asDoorState() + DoorState& asDoorState() final { return *this; } - virtual const DoorState& asDoorState() const + const DoorState& asDoorState() const final { return *this; } diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 07611c32f..ff2fbf66d 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -122,7 +122,7 @@ namespace ESM for (int i=0; i<10; ++i) { mData.mRankData[i].mAttribute1 = mData.mRankData[i].mAttribute2 = 0; - mData.mRankData[i].mSkill1 = mData.mRankData[i].mSkill2 = 0; + mData.mRankData[i].mPrimarySkill = mData.mRankData[i].mFavouredSkill = 0; mData.mRankData[i].mFactReaction = 0; mRanks[i].clear(); diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index cc715d266..098ed4309 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -19,10 +19,11 @@ struct RankData { int mAttribute1, mAttribute2; // Attribute level - int mSkill1, mSkill2; // Skill level (faction skills given in + // Skill level (faction skills given in // skillID below.) You need one skill at - // level 'skill1' and two skills at level - // 'skill2' to advance to this rank. + // level 'mPrimarySkill' and two skills at level + // 'mFavouredSkill' to advance to this rank. + int mPrimarySkill, mFavouredSkill; int mFactReaction; // Reaction from faction members }; diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp index 4ae026da6..6c0469050 100644 --- a/components/esm/npcstate.hpp +++ b/components/esm/npcstate.hpp @@ -10,7 +10,7 @@ namespace ESM { // format 0, saved games only - struct NpcState : public ObjectState + struct NpcState final : public ObjectState { InventoryState mInventory; NpcStats mNpcStats; @@ -19,14 +19,14 @@ namespace ESM /// Initialize to default state void blank(); - virtual void load (ESMReader &esm); - virtual void save (ESMWriter &esm, bool inInventory = false) const; + void load (ESMReader &esm) final; + void save (ESMWriter &esm, bool inInventory = false) const final; - virtual NpcState& asNpcState() + NpcState& asNpcState() final { return *this; } - virtual const NpcState& asNpcState() const + const NpcState& asNpcState() const final { return *this; } diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index c5fa2a09e..277335e8c 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -31,8 +31,9 @@ void ESM::NpcStats::load (ESMReader &esm) mDisposition = 0; esm.getHNOT (mDisposition, "DISP"); + bool intFallback = esm.getFormat() < 11; for (int i=0; i<27; ++i) - mSkills[i].load (esm); + mSkills[i].load (esm, intFallback); mWerewolfDeprecatedData = false; if (esm.getFormat() < 8 && esm.peekNextSub("STBA")) @@ -40,17 +41,17 @@ void ESM::NpcStats::load (ESMReader &esm) // we have deprecated werewolf skills, stored interleaved // Load into one big vector, then remove every 2nd value mWerewolfDeprecatedData = true; - std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); + std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); for (int i=0; i<27; ++i) { - ESM::StatState skill; - skill.load(esm); + ESM::StatState skill; + skill.load(esm, intFallback); skills.push_back(skill); } int i=0; - for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) + for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) { if (i%2 == 1) it = skills.erase(it); @@ -68,7 +69,7 @@ void ESM::NpcStats::load (ESMReader &esm) { ESM::StatState dummy; for (int i=0; i<8; ++i) - dummy.load(esm); + dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; } diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 467a099ce..3ad94b543 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -31,7 +31,7 @@ namespace ESM std::map mFactions; // lower case IDs int mDisposition; - StatState mSkills[27]; + StatState mSkills[27]; int mBounty; int mReputation; int mWerewolfKills; diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index a7a452c82..9709bf4ff 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -26,6 +26,7 @@ void ESM::ObjectState::load (ESMReader &esm) mCount = 1; esm.getHNOT (mCount, "COUN"); + mPosition = mRef.mPos; esm.getHNOT (mPosition, "POS_", 24); if (esm.isNextSub("LROT")) @@ -61,7 +62,7 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const if (mCount!=1) esm.writeHNT ("COUN", mCount); - if (!inInventory) + if (!inInventory && mPosition != mRef.mPos) esm.writeHNT ("POS_", mPosition, 24); if (mFlags != 0) diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 571a10a8c..e2e9219e2 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -21,8 +21,9 @@ void ESM::Player::load (ESMReader &esm) else mHasMark = false; - mAutoMove = 0; - esm.getHNOT (mAutoMove, "AMOV"); + // Automove, no longer used. + if (esm.isNextSub("AMOV")) + esm.skipHSub(); mBirthsign = esm.getHNString ("SIGN"); @@ -43,12 +44,13 @@ void ESM::Player::load (ESMReader &esm) checkPrevItems = false; } + bool intFallback = esm.getFormat() < 11; if (esm.hasMoreSubs()) { for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; - StatState mSaveSkills[ESM::Skill::Length]; + StatState mSaveAttributes[ESM::Attribute::Length]; + StatState mSaveSkills[ESM::Skill::Length]; typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index f2ebc7bf0..76695dbe8 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -2,10 +2,9 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 8; +int ESM::SavedGame::sCurrentFormat = 12; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index aa0429657..26efae824 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -4,6 +4,8 @@ #include #include +#include "defs.hpp" + namespace ESM { class ESMReader; @@ -17,14 +19,6 @@ namespace ESM static int sCurrentFormat; - struct TimeStamp - { - float mGameHour; - int mDay; - int mMonth; - int mYear; - }; - std::vector mContentFiles; std::string mPlayerName; int mPlayerLevel; @@ -36,7 +30,7 @@ namespace ESM std::string mPlayerClassName; std::string mPlayerCell; - TimeStamp mInGameTime; + EpochTimeStamp mInGameTime; double mTimePlayed; std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index a21078e10..2eb1e7867 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -33,23 +33,36 @@ namespace ESM mSpells[id] = state; } + // Obsolete while (esm.isNextSub("PERM")) { std::string spellId = esm.getHString(); - std::vector permEffectList; - while (esm.isNextSub("EFID")) + + while (true) { + ESM_Context restorePoint = esm.getContext(); + + if (!esm.isNextSub("EFID")) + break; + PermanentSpellEffectInfo info; esm.getHT(info.mId); - esm.getHNT(info.mArg, "ARG_"); - esm.getHNT(info.mMagnitude, "MAGN"); + if (esm.isNextSub("BASE")) + { + esm.restoreContext(restorePoint); + return; + } + else + esm.getHNT(info.mArg, "ARG_"); + esm.getHNT(info.mMagnitude, "MAGN"); permEffectList.push_back(info); } mPermanentSpellEffects[spellId] = permEffectList; } + // Obsolete while (esm.isNextSub("CORP")) { std::string id = esm.getHString(); @@ -91,19 +104,6 @@ namespace ESM esm.writeHNT("PURG", *pIt); } - for (std::map >::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) - { - esm.writeHNString("PERM", it->first); - - const std::vector & effects = it->second; - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) - { - esm.writeHNT("EFID", effectIt->mId); - esm.writeHNT("ARG_", effectIt->mArg); - esm.writeHNT("MAGN", effectIt->mMagnitude); - } - } - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) { esm.writeHNString("CORP", it->first); diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index ec613afab..55c57611a 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -29,15 +29,16 @@ namespace ESM float mMagnitude; }; - struct SpellParams { + struct SpellParams + { std::map mEffectRands; std::set mPurgedEffects; }; typedef std::map TContainer; TContainer mSpells; + // FIXME: obsolete, used only for old saves std::map > mPermanentSpellEffects; - std::map mCorprusSpells; std::map mUsedPowers; diff --git a/components/esm/statstate.cpp b/components/esm/statstate.cpp index c17bedd81..b9ddc3efd 100644 --- a/components/esm/statstate.cpp +++ b/components/esm/statstate.cpp @@ -9,19 +9,44 @@ namespace ESM StatState::StatState() : mBase(0), mMod(0), mCurrent(0), mDamage(0), mProgress(0) {} template - void StatState::load(ESMReader &esm) + void StatState::load(ESMReader &esm, bool intFallback) { - esm.getHNT(mBase, "STBA"); + // We changed stats values from integers to floats; ensure backwards compatibility + if (intFallback) + { + int base = 0; + esm.getHNT(base, "STBA"); + mBase = static_cast(base); - mMod = 0; - esm.getHNOT(mMod, "STMO"); - mCurrent = 0; - esm.getHNOT(mCurrent, "STCU"); + int mod = 0; + esm.getHNOT(mod, "STMO"); + mMod = static_cast(mod); - // mDamage was changed to a float; ensure backwards compatibility - T oldDamage = 0; - esm.getHNOT(oldDamage, "STDA"); - mDamage = static_cast(oldDamage); + int current = 0; + esm.getHNOT(current, "STCU"); + mCurrent = static_cast(current); + + // mDamage was changed to a float; ensure backwards compatibility + int oldDamage = 0; + esm.getHNOT(oldDamage, "STDA"); + mDamage = static_cast(oldDamage); + } + else + { + mBase = 0; + esm.getHNT(mBase, "STBA"); + + mMod = 0; + esm.getHNOT(mMod, "STMO"); + + mCurrent = 0; + esm.getHNOT(mCurrent, "STCU"); + + mDamage = 0; + esm.getHNOT(mDamage, "STDF"); + + mProgress = 0; + } esm.getHNOT(mDamage, "STDF"); diff --git a/components/esm/statstate.hpp b/components/esm/statstate.hpp index 47aeb0331..d81d24a61 100644 --- a/components/esm/statstate.hpp +++ b/components/esm/statstate.hpp @@ -20,7 +20,7 @@ namespace ESM StatState(); - void load (ESMReader &esm); + void load (ESMReader &esm, bool intFallback = false); void save (ESMWriter &esm) const; }; } diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 7f15a7efe..92d1a9ff0 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include namespace bconv = boost::locale::conv; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index dae83a1f9..605d55243 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -21,6 +21,8 @@ #include +#include + namespace { unsigned long utf8ToUnicode(const std::string& utf8) @@ -147,15 +149,31 @@ namespace Gui FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, const std::string& userDataPath) : mVFS(vfs) , mUserDataPath(userDataPath) + , mFontHeight(16) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; else mEncoding = encoding; + + int fontSize = Settings::Manager::getInt("font size", "GUI"); + mFontHeight = std::min(std::max(12, fontSize), 20); + + MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); + MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = MyGUI::newDelegate(this, &FontLoader::loadFontFromXml); } FontLoader::~FontLoader() { + try + { + MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the FontLoader destructor: " << e.what(); + } + for (std::vector::iterator it = mTextures.begin(); it != mTextures.end(); ++it) delete *it; mTextures.clear(); @@ -190,7 +208,7 @@ namespace Gui { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) - loadFont(name, exportToFile); + loadBitmapFont(name, exportToFile); } else break; @@ -238,7 +256,7 @@ namespace Gui float ascent; } GlyphInfo; - void FontLoader::loadFont(const std::string &fileName, bool exportToFile) + void FontLoader::loadBitmapFont(const std::string &fileName, bool exportToFile) { Files::IStreamPtr file = mVFS->get(fileName); @@ -527,4 +545,96 @@ namespace Gui MyGUI::ResourceManager::getInstance().addResource(bookFont); } + void FontLoader::loadFontFromXml(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) + { + MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); + bool createCopy = false; + while (resourceNode.next("Resource")) + { + std::string type, name; + resourceNode->findAttribute("type", type); + resourceNode->findAttribute("name", name); + + if (name.empty()) + continue; + + if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) + { + createCopy = true; + + // For TrueType fonts we should override Size and Resolution properties + // to allow to configure font size via config file, without need to edit XML files. + // Also we should take UI scaling factor in account. + int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); + resolution = std::min(960, std::max(48, resolution)); + + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + resolution *= uiScale; + + MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); + resolutionNode->addAttribute("key", "Resolution"); + resolutionNode->addAttribute("value", std::to_string(resolution)); + + MyGUI::xml::ElementPtr sizeNode = resourceNode->createChild("Property"); + sizeNode->addAttribute("key", "Size"); + sizeNode->addAttribute("value", std::to_string(mFontHeight)); + } + else if (Misc::StringUtils::ciEqual(type, "ResourceSkin") || + Misc::StringUtils::ciEqual(type, "AutoSizedResourceSkin")) + { + // We should adjust line height for MyGUI widgets depending on font size + MyGUI::xml::ElementPtr heightNode = resourceNode->createChild("Property"); + heightNode->addAttribute("key", "HeightLine"); + heightNode->addAttribute("value", std::to_string(mFontHeight+2)); + } + } + + MyGUI::ResourceManager::getInstance().loadFromXmlNode(_node, _file, _version); + + if (createCopy) + { + MyGUI::xml::ElementPtr copy = _node->createCopy(); + + MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); + while (copyFont.next("Resource")) + { + std::string type, name; + copyFont->findAttribute("type", type); + copyFont->findAttribute("name", name); + + if (name.empty()) + continue; + + if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) + { + // Since the journal and books use the custom scaling factor depending on resolution, + // setup separate fonts with different Resolution to fit these windows. + // These fonts have an internal prefix. + int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); + resolution = std::min(960, std::max(48, resolution)); + + float currentX = Settings::Manager::getInt("resolution x", "Video"); + float currentY = Settings::Manager::getInt("resolution y", "Video"); + // TODO: read size from openmw_layout.xml somehow + float heightScale = (currentY / 520); + float widthScale = (currentX / 600); + float uiScale = std::min(widthScale, heightScale); + resolution *= uiScale; + + MyGUI::xml::ElementPtr resolutionNode = copyFont->createChild("Property"); + resolutionNode->addAttribute("key", "Resolution"); + resolutionNode->addAttribute("value", std::to_string(resolution)); + + copyFont->setAttribute("name", "Journalbook " + name); + } + } + + MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); + } + } + + int FontLoader::getFontHeight() + { + return mFontHeight; + } } diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp index 39301f9f5..94b022501 100644 --- a/components/fontloader/fontloader.hpp +++ b/components/fontloader/fontloader.hpp @@ -3,6 +3,9 @@ #include "boost/filesystem/operations.hpp" +#include +#include + #include #include @@ -19,8 +22,6 @@ namespace MyGUI namespace Gui { - - /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and OSG /// @note The FontLoader needs to remain in scope as long as you want to use the loaded fonts. class FontLoader @@ -33,16 +34,21 @@ namespace Gui void loadBitmapFonts (bool exportToFile); void loadTrueTypeFonts (); + void loadFontFromXml(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); + + int getFontHeight(); + private: ToUTF8::FromType mEncoding; const VFS::Manager* mVFS; std::string mUserDataPath; + int mFontHeight; std::vector mTextures; std::vector mFonts; /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? - void loadFont (const std::string& fileName, bool exportToFile); + void loadBitmapFont (const std::string& fileName, bool exportToFile); FontLoader(const FontLoader&); void operator=(const FontLoader&); diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 88fdae128..862018bdc 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -35,8 +35,6 @@ namespace Interpreter virtual void report (const std::string& message) = 0; - virtual bool menuMode() = 0; - virtual int getGlobalShort (const std::string& name) const = 0; virtual int getGlobalLong (const std::string& name) const = 0; @@ -79,23 +77,6 @@ namespace Interpreter virtual std::string getCurrentCellName() const = 0; - virtual bool isScriptRunning (const std::string& name) const = 0; - - virtual void startScript (const std::string& name, const std::string& targetId = "") = 0; - - virtual void stopScript (const std::string& name) = 0; - - virtual float getDistance (const std::string& name, const std::string& id = "") const - = 0; - - virtual float getSecondsPassed() const = 0; - - virtual bool isDisabled (const std::string& id = "") const = 0; - - virtual void enable (const std::string& id = "") = 0; - - virtual void disable (const std::string& id = "") = 0; - virtual int getMemberShort (const std::string& id, const std::string& name, bool global) const = 0; virtual int getMemberLong (const std::string& id, const std::string& name, bool global) const = 0; diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt index 5d1eba088..b5c9cf0ae 100644 --- a/components/interpreter/docs/vmformat.txt +++ b/components/interpreter/docs/vmformat.txt @@ -96,27 +96,14 @@ op 34: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser than op 35: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser or equal, 0 else op 36: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater than, 0 else op 37: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater or equal, 0 else -op 38: push 1 if game is in menu mode, 0 else +opcode 38 unused op 39: store stack[0] in global short stack[1] and pop twice op 40: store stack[0] in global long stack[1] and pop twice op 41: store stack[0] in global float stack[1] and pop twice op 42: replace stack[0] with global short stack[0] op 43: replace stack[0] with global long stack[0] op 44: replace stack[0] with global float stack[0] -op 45: replace stack[0] with a random integer value in the range [0, stack[0]-1] -op 46: replace stack[0] with 1, if global script stack[0] is running, 0 else -op 47: start script stack[0] and pop -op 48: stop script stack[0] and pop -op 49: replace stack[0] with distance between implicit reference and a reference of ID stack[0] -op 50: push frame duration (float) -op 51: enable implicit reference -op 52: disable implicit reference -op 53: push 1, if implicit reference is disabled, 0 else -op 54: explicit reference = stack[0]; pop; enable explicit reference -op 55: explicit reference = stack[0]; pop; disable explicit reference -op 56: explicit reference = stack[0]; pop; push 1, if explicit reference is disabled, 0 else -op 57: explicit reference = stack[0]; pop; - replace stack[0] with distance between explicit reference and a reference of ID stack[0] +opcodes 45-57 unused op 58: report string literal index in stack[0]; additional arguments (if any) in stack[n]..stack[1]; n is determined according to the message string @@ -133,6 +120,5 @@ op 67: store stack[0] in member float stack[2] of global script with ID stack[1] op 68: replace stack[0] with member short stack[1] of global script with ID stack[0] op 69: replace stack[0] with member short stack[1] of global script with ID stack[0] op 70: replace stack[0] with member short stack[1] of global script with ID stack[0] -op 71: explicit reference (target) = stack[0]; pop; start script stack[0] and pop -opcodes 72-33554431 unused +opcodes 71-33554431 unused opcodes 33554432-67108863 reserved for extensions diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index 31e911f8b..afee36bc2 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -8,8 +8,6 @@ #include "mathopcodes.hpp" #include "controlopcodes.hpp" #include "miscopcodes.hpp" -#include "scriptopcodes.hpp" -#include "spatialopcodes.hpp" namespace Interpreter { @@ -97,25 +95,6 @@ namespace Interpreter // misc interpreter.installSegment3 (0, new OpMessageBox); - interpreter.installSegment5 (38, new OpMenuMode); - interpreter.installSegment5 (45, new OpRandom); - interpreter.installSegment5 (50, new OpGetSecondsPassed); - interpreter.installSegment5 (51, new OpEnable); - interpreter.installSegment5 (52, new OpDisable); - interpreter.installSegment5 (53, new OpGetDisabled); - interpreter.installSegment5 (54, new OpEnableExplicit); - interpreter.installSegment5 (55, new OpDisableExplicit); - interpreter.installSegment5 (56, new OpGetDisabledExplicit); interpreter.installSegment5 (58, new OpReport); - - // script control - interpreter.installSegment5 (46, new OpScriptRunning); - interpreter.installSegment5 (47, new OpStartScript); - interpreter.installSegment5 (48, new OpStopScript); - interpreter.installSegment5 (71, new OpStartScriptExplicit); - - // spacial - interpreter.installSegment5 (49, new OpGetDistance); - interpreter.installSegment5 (57, new OpGetDistanceExplicit); } } diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index a77e0d7d8..07bd84ec9 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -11,7 +11,6 @@ #include "runtime.hpp" #include "defines.hpp" -#include #include namespace Interpreter @@ -168,116 +167,6 @@ namespace Interpreter } }; - class OpMenuMode : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - runtime.push (runtime.getContext().menuMode()); - } - }; - - class OpRandom : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - Type_Integer limit = runtime[0].mInteger; - - if (limit<0) - throw std::runtime_error ( - "random: argument out of range (Don't be so negative!)"); - - runtime[0].mFloat = static_cast(Misc::Rng::rollDice(limit)); // [o, limit) - } - }; - - class OpGetSecondsPassed : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - Type_Float duration = runtime.getContext().getSecondsPassed(); - - runtime.push (duration); - } - }; - - class OpEnable : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - runtime.getContext().enable(); - } - }; - - class OpDisable : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - runtime.getContext().disable(); - } - }; - - class OpGetDisabled : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - runtime.push (runtime.getContext().isDisabled()); - } - }; - - class OpEnableExplicit : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - int index = runtime[0].mInteger; - runtime.pop(); - std::string id = runtime.getStringLiteral (index); - - runtime.getContext().enable (id); - } - }; - - class OpDisableExplicit : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - int index = runtime[0].mInteger; - runtime.pop(); - std::string id = runtime.getStringLiteral (index); - - runtime.getContext().disable (id); - } - }; - - class OpGetDisabledExplicit : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - int index = runtime[0].mInteger; - runtime.pop(); - std::string id = runtime.getStringLiteral (index); - - runtime.push (runtime.getContext().isDisabled (id)); - } - }; - } #endif diff --git a/components/interpreter/scriptopcodes.hpp b/components/interpreter/scriptopcodes.hpp deleted file mode 100644 index c98bcd23e..000000000 --- a/components/interpreter/scriptopcodes.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef INTERPRETER_SCRIPTOPCODES_H_INCLUDED -#define INTERPRETER_SCRIPTOPCODES_H_INCLUDED - -#include "opcodes.hpp" -#include "runtime.hpp" -#include "context.hpp" - -namespace Interpreter -{ - class OpScriptRunning : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime[0].mInteger = runtime.getContext().isScriptRunning (name); - } - }; - - class OpStartScript : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - runtime.getContext().startScript (name); - } - }; - - class OpStartScriptExplicit : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - std::string targetId = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - runtime.getContext().startScript (name, targetId); - } - }; - - class OpStopScript : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - runtime.getContext().stopScript (name); - } - }; -} - -#endif - diff --git a/components/interpreter/spatialopcodes.hpp b/components/interpreter/spatialopcodes.hpp deleted file mode 100644 index e37df8116..000000000 --- a/components/interpreter/spatialopcodes.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef INTERPRETER_SPATIALOPCODES_H_INCLUDED -#define INTERPRETER_SPATIALOPCODES_H_INCLUDED - -#include "opcodes.hpp" -#include "runtime.hpp" - -namespace Interpreter -{ - class OpGetDistance : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - - Type_Float distance = runtime.getContext().getDistance (name); - - runtime[0].mFloat = distance; - } - }; - - class OpGetDistanceExplicit : public Opcode0 - { - public: - - virtual void execute (Runtime& runtime) - { - int index = runtime[0].mInteger; - runtime.pop(); - std::string id = runtime.getStringLiteral (index); - - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - - Type_Float distance = runtime.getContext().getDistance (name, id); - - runtime[0].mFloat = distance; - } - }; -} - -#endif - diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index af43eb414..1053b1c56 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -24,6 +24,9 @@ const float GravityConst = 8.96f; // Size of one exterior cell in game units const int CellSizeInUnits = 8192; +// Size of active cell grid in cells (it is a square with the (2 * CellGridRadius + 1) cells side) +const int CellGridRadius = 1; + // A label to mark night/day visual switches const std::string NightDayLabel = "NightDaySwitch"; diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index c5671f016..c5784d33a 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H +#include + #include #include #include @@ -21,6 +23,11 @@ namespace Convert return osg::Vec3f(value.x(), value.y(), value.z()); } + inline osg::Vec3f makeOsgVec3f(const ESM::Pathgrid::Point& value) + { + return osg::Vec3f(value.mX, value.mY, value.mZ); + } + inline btVector3 toBullet(const osg::Vec3f& vec) { return btVector3(vec.x(), vec.y(), vec.z()); diff --git a/components/misc/coordinateconverter.hpp b/components/misc/coordinateconverter.hpp new file mode 100644 index 000000000..190641415 --- /dev/null +++ b/components/misc/coordinateconverter.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H +#define OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H + +#include +#include +#include +#include + +namespace Misc +{ + /// \brief convert coordinates between world and local cell + class CoordinateConverter + { + public: + CoordinateConverter(bool exterior, int cellX, int cellY) + : mCellX(exterior ? cellX * ESM::Land::REAL_SIZE : 0), + mCellY(exterior ? cellY * ESM::Land::REAL_SIZE : 0) + { + } + + explicit CoordinateConverter(const ESM::Cell* cell) + : CoordinateConverter(cell->isExterior(), cell->mData.mX, cell->mData.mY) + { + } + + /// in-place conversion from local to world + void toWorld(ESM::Pathgrid::Point& point) const + { + point.mX += mCellX; + point.mY += mCellY; + } + + ESM::Pathgrid::Point toWorldPoint(ESM::Pathgrid::Point point) const + { + toWorld(point); + return point; + } + + /// in-place conversion from local to world + void toWorld(osg::Vec3f& point) const + { + point.x() += static_cast(mCellX); + point.y() += static_cast(mCellY); + } + + /// in-place conversion from world to local + void toLocal(osg::Vec3f& point) const + { + point.x() -= static_cast(mCellX); + point.y() -= static_cast(mCellY); + } + + osg::Vec3f toLocalVec3(const osg::Vec3f& point) const + { + return osg::Vec3f( + point.x() - static_cast(mCellX), + point.y() - static_cast(mCellY), + point.z() + ); + } + + private: + int mCellX; + int mCellY; + }; +} + +#endif diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 8ae49476b..afb304bad 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -47,10 +47,9 @@ void NiGeometryData::read(NIFStream *nif) if (nif->getBoolean()) nif->getVector4s(colors, verts); - // Only the first 6 bits are used as a count. I think the rest are - // flags of some sort. + // In Morrowind this field only corresponds to the number of UV sets. + // NifTools research is inaccurate. int uvs = nif->getUShort(); - uvs &= 0x3f; if(nif->getInt()) { @@ -110,6 +109,32 @@ void NiTriStripsData::read(NIFStream *nif) nif->getUShorts(strips[i], lengths[i]); } +void NiLinesData::read(NIFStream *nif) +{ + NiGeometryData::read(nif); + size_t num = vertices.size(); + std::vector flags; + nif->getChars(flags, num); + // Can't construct a line from a single vertex. + if (num < 2) + return; + // Convert connectivity flags into usable geometry. The last element needs special handling. + for (size_t i = 0; i < num-1; ++i) + { + if (flags[i] & 1) + { + lines.emplace_back(i); + lines.emplace_back(i+1); + } + } + // If there are just two vertices, they can be connected twice. Probably isn't critical. + if (flags[num-1] & 1) + { + lines.emplace_back(num-1); + lines.emplace_back(0); + } +} + void NiAutoNormalParticlesData::read(NIFStream *nif) { NiGeometryData::read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 33818810a..66b3f693a 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -62,6 +62,14 @@ public: void read(NIFStream *nif); }; +struct NiLinesData : public NiGeometryData +{ + // Lines, series of indices that correspond to connected vertices. + std::vector lines; + + void read(NIFStream *nif); +}; + class NiAutoNormalParticlesData : public NiGeometryData { public: diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 13c9ced60..b33f0c051 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -54,6 +54,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); newFactory.insert(makeEntry("NiTriShape", &construct , RC_NiTriShape )); newFactory.insert(makeEntry("NiTriStrips", &construct , RC_NiTriStrips )); + newFactory.insert(makeEntry("NiLines", &construct , RC_NiLines )); newFactory.insert(makeEntry("NiRotatingParticles", &construct , RC_NiRotatingParticles )); newFactory.insert(makeEntry("NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles )); newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); @@ -97,6 +98,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiFloatData", &construct , RC_NiFloatData )); newFactory.insert(makeEntry("NiTriShapeData", &construct , RC_NiTriShapeData )); newFactory.insert(makeEntry("NiTriStripsData", &construct , RC_NiTriStripsData )); + newFactory.insert(makeEntry("NiLinesData", &construct , RC_NiLinesData )); newFactory.insert(makeEntry("NiVisData", &construct , RC_NiVisData )); newFactory.insert(makeEntry("NiColorData", &construct , RC_NiColorData )); newFactory.insert(makeEntry("NiPixelData", &construct , RC_NiPixelData )); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 06a1a3b76..e605df32a 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -182,6 +182,26 @@ struct NiTriStrips : NiGeometry } }; +struct NiLines : NiGeometry +{ + NiLinesDataPtr data; + + void read(NIFStream *nif) + { + Node::read(nif); + data.read(nif); + skin.read(nif); + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + skin.post(nif); + if (!skin.empty()) + nif->setUseSkinning(true); + } +}; struct NiCamera : Node { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 074dea9cf..67202d2fe 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -43,6 +43,7 @@ enum RecordType RC_NiCollisionSwitch, RC_NiTriShape, RC_NiTriStrips, + RC_NiLines, RC_NiRotatingParticles, RC_NiAutoNormalParticles, RC_NiBSParticleNode, @@ -83,6 +84,7 @@ enum RecordType RC_NiFloatData, RC_NiTriShapeData, RC_NiTriStripsData, + RC_NiLinesData, RC_NiVisData, RC_NiColorData, RC_NiPixelData, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 478ecfdbb..57607cb6a 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -142,6 +142,7 @@ class NiRotatingParticlesData; class NiAutoNormalParticlesData; class NiPalette; struct NiParticleModifier; +struct NiLinesData; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -158,6 +159,7 @@ using NiColorDataPtr = RecordPtrT; using NiKeyframeDataPtr = RecordPtrT; using NiTriShapeDataPtr = RecordPtrT; using NiTriStripsDataPtr = RecordPtrT; +using NiLinesDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; using NiRotatingParticlesDataPtr = RecordPtrT; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index a088ead4c..3c95394a6 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -352,8 +352,9 @@ void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) } } -AlphaController::AlphaController(const Nif::NiFloatData *data) +AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial) : mData(data->mKeyList, 1.f) + , mBaseMaterial(baseMaterial) { } @@ -365,14 +366,13 @@ AlphaController::AlphaController() AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) + , mBaseMaterial(copy.mBaseMaterial) { } void AlphaController::setDefaults(osg::StateSet *stateset) { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) @@ -387,9 +387,10 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) } } -MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color) +MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial) : mData(data->mKeyList, osg::Vec3f(1,1,1)) , mTargetColor(color) + , mBaseMaterial(baseMaterial) { } @@ -401,14 +402,13 @@ MaterialColorController::MaterialColorController(const MaterialColorController & : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) , mTargetColor(copy.mTargetColor) + , mBaseMaterial(copy.mBaseMaterial) { } void MaterialColorController::setDefaults(osg::StateSet *stateset) { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 3f66013a2..c81f97a71 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -24,6 +24,7 @@ namespace osg { class Node; class StateSet; + class Material; } namespace osgParticle @@ -268,9 +269,9 @@ namespace NifOsg { private: FloatInterpolator mData; - + osg::ref_ptr mBaseMaterial; public: - AlphaController(const Nif::NiFloatData *data); + AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial); AlphaController(); AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); @@ -291,7 +292,7 @@ namespace NifOsg Specular = 2, Emissive = 3 }; - MaterialColorController(const Nif::NiPosData *data, TargetColor color); + MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial); MaterialColorController(); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); @@ -304,6 +305,7 @@ namespace NifOsg private: Vec3Interpolator mData; TargetColor mTargetColor = Ambient; + osg::ref_ptr mBaseMaterial; }; class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 521764274..88e89b400 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -157,7 +157,8 @@ namespace nextpos = std::distance(str.begin(), ++last); } std::string result = str.substr(pos, nextpos-pos); - textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::lowerCase(result))); + Misc::StringUtils::lowerCaseInPlace(result); + textkeys.emplace(tk->list[i].time, std::move(result)); pos = nextpos; } @@ -495,14 +496,6 @@ namespace NifOsg switch (nifNode->recType) { - case Nif::RC_NiAutoNormalParticles: - case Nif::RC_NiRotatingParticles: - // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. - // No support for keyframe controllers (just crashes in the original engine). - if (nifNode->trafo.isIdentity()) - node = new osg::Group; - dataVariance = osg::Object::STATIC; - break; case Nif::RC_NiBillboardNode: dataVariance = osg::Object::DYNAMIC; break; @@ -519,17 +512,9 @@ namespace NifOsg // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you try). if (!nifNode->parent && nifNode->controller.empty() && nifNode->trafo.isIdentity()) - { node = new osg::Group; - dataVariance = osg::Object::STATIC; - } - else - { - dataVariance = (nifNode->controller.empty() ? osg::Object::STATIC : osg::Object::DYNAMIC); - } - if (nifNode->isBone) - dataVariance = osg::Object::DYNAMIC; + dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; break; } @@ -542,7 +527,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; @@ -554,9 +539,6 @@ namespace NifOsg node->addCullCallback(new BillboardCallback); } - if (!nifNode->controller.empty() && nifNode->controller->recType == Nif::RC_NiKeyframeController) - isAnimated = true; - node->setName(nifNode->name); if (parentNode) @@ -626,21 +608,13 @@ namespace NifOsg node->setNodeMask(Loader::getHiddenNodeMask()); } - if ((skipMeshes || hasMarkers) && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. - { - node->setDataVariance(osg::Object::DYNAMIC); - } - - if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && isAnimated) // Same thing for animated shapes - { - node->setDataVariance(osg::Object::DYNAMIC); - } - osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); - if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && !skipMeshes) + const bool isGeometry = nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines; + + if (isGeometry && !skipMeshes) { const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name); static const std::string markerName = "tri editormarker"; @@ -652,9 +626,9 @@ namespace NifOsg Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) - handleTriShape(nifNode, node, composite, boundTextures, animflags); + handleGeometry(nifNode, node, composite, boundTextures, animflags); else - handleSkinnedTriShape(nifNode, node, composite, boundTextures, animflags); + handleSkinnedGeometry(nifNode, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); @@ -665,11 +639,22 @@ namespace NifOsg handleParticleSystem(nifNode, node, composite, animflags, rootNode); if (composite->getNumControllers() > 0) - node->addUpdateCallback(composite); + { + osg::Callback *cb = composite; + if (composite->getNumControllers() == 1) + cb = composite->getController(0); + if (animflags & Nif::NiNode::AnimFlag_AutoPlay) + node->addCullCallback(cb); + else + node->addUpdateCallback(cb); // have to remain as UpdateCallback so AssignControllerSourcesVisitor can find it. + } - if (nifNode->recType != Nif::RC_NiTriShape && nifNode->recType != Nif::RC_NiTriStrips - && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) - handleNodeControllers(nifNode, static_cast(node.get()), animflags); + bool isAnimated = false; + handleNodeControllers(nifNode, node, animflags, isAnimated); + hasAnimatedParents |= isAnimated; + // Make sure empty nodes are not optimized away so the physics system can find them. + if (isAnimated || (hasAnimatedParents && (skipMeshes || hasMarkers))) + node->setDataVariance(osg::Object::DYNAMIC); // LOD and Switch nodes must be wrapped by a transform (the current node) to support transformations properly // and we need to attach their children to the osg::LOD/osg::Switch nodes @@ -710,7 +695,7 @@ namespace NifOsg for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); + handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); } } @@ -741,40 +726,10 @@ namespace NifOsg setupController(niuvctrl, uvctrl, animflags); composite->addController(uvctrl); } - else if (ctrl->recType == Nif::RC_NiKeyframeController) - { - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - if(!key->data.empty()) - { - osg::ref_ptr callback(new KeyframeController(key->data.getPtr())); - - setupController(key, callback, animflags); - node->addUpdateCallback(callback); - } - } - else if (ctrl->recType == Nif::RC_NiPathController) - { - const Nif::NiPathController *path = static_cast(ctrl.getPtr()); - if (!path->posData.empty() && !path->floatData.empty()) - { - osg::ref_ptr callback(new PathController(path)); - - setupController(path, callback, animflags); - node->addUpdateCallback(callback); - } - } - else if (ctrl->recType == Nif::RC_NiVisController) - { - handleVisController(static_cast(ctrl.getPtr()), node, animflags); - } - else if(ctrl->recType == Nif::RC_NiGeomMorpherController) - {} // handled in handleTriShape - else - Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } } - void handleNodeControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode, int animflags) + void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -783,57 +738,55 @@ namespace NifOsg if (ctrl->recType == Nif::RC_NiKeyframeController) { const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - if(!key->data.empty()) - { - osg::ref_ptr callback(new KeyframeController(key->data.getPtr())); - - setupController(key, callback, animflags); - transformNode->addUpdateCallback(callback); - } + if (key->data.empty()) + continue; + osg::ref_ptr callback(new KeyframeController(key->data.getPtr())); + setupController(key, callback, animflags); + node->addUpdateCallback(callback); + isAnimated = true; } else if (ctrl->recType == Nif::RC_NiPathController) { const Nif::NiPathController *path = static_cast(ctrl.getPtr()); - if (!path->posData.empty() && !path->floatData.empty()) - { - osg::ref_ptr callback(new PathController(path)); - - setupController(path, callback, animflags); - transformNode->addUpdateCallback(callback); - } + if (path->posData.empty() || path->floatData.empty()) + continue; + osg::ref_ptr callback(new PathController(path)); + setupController(path, callback, animflags); + node->addUpdateCallback(callback); + isAnimated = true; } else if (ctrl->recType == Nif::RC_NiVisController) { - handleVisController(static_cast(ctrl.getPtr()), transformNode, animflags); + const Nif::NiVisController *visctrl = static_cast(ctrl.getPtr()); + if (visctrl->data.empty()) + continue; + osg::ref_ptr callback(new VisController(visctrl->data.getPtr(), Loader::getHiddenNodeMask())); + setupController(visctrl, callback, animflags); + node->addUpdateCallback(callback); } else if (ctrl->recType == Nif::RC_NiRollController) { - handleRollController(static_cast(ctrl.getPtr()), transformNode, animflags); + const Nif::NiRollController *rollctrl = static_cast(ctrl.getPtr()); + if (rollctrl->data.empty()) + continue; + osg::ref_ptr callback(new RollController(rollctrl->data.getPtr())); + setupController(rollctrl, callback, animflags); + node->addUpdateCallback(callback); + isAnimated = true; + } + else if (ctrl->recType == Nif::RC_NiGeomMorpherController + || ctrl->recType == Nif::RC_NiParticleSystemController + || ctrl->recType == Nif::RC_NiBSPArrayController + || ctrl->recType == Nif::RC_NiUVController) + { + // These controllers are handled elsewhere } else Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } } - void handleVisController(const Nif::NiVisController* visctrl, osg::Node* node, int animflags) - { - if (visctrl->data.empty()) - return; - osg::ref_ptr callback(new VisController(visctrl->data.getPtr(), Loader::getHiddenNodeMask())); - setupController(visctrl, callback, animflags); - node->addUpdateCallback(callback); - } - - void handleRollController(const Nif::NiRollController* rollctrl, osg::Node* node, int animflags) - { - if (rollctrl->data.empty()) - return; - osg::ref_ptr callback(new RollController(rollctrl->data.getPtr())); - setupController(rollctrl, callback, animflags); - node->addUpdateCallback(callback); - } - - void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) + void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -844,7 +797,7 @@ namespace NifOsg const Nif::NiAlphaController* alphactrl = static_cast(ctrl.getPtr()); if (alphactrl->data.empty()) continue; - osg::ref_ptr osgctrl(new AlphaController(alphactrl->data.getPtr())); + osg::ref_ptr osgctrl(new AlphaController(alphactrl->data.getPtr(), baseMaterial)); setupController(alphactrl, osgctrl, animflags); composite->addController(osgctrl); } @@ -854,7 +807,7 @@ namespace NifOsg if (matctrl->data.empty()) continue; auto targetColor = static_cast(matctrl->targetColor); - osg::ref_ptr osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor)); + osg::ref_ptr osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial)); setupController(matctrl, osgctrl, animflags); composite->addController(osgctrl); } @@ -927,6 +880,8 @@ namespace NifOsg else if (affectors->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier *cl = static_cast(affectors.getPtr()); + if (cl->data.empty()) + continue; const Nif::NiColorData *clrdata = cl->data.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } @@ -1069,8 +1024,6 @@ namespace NifOsg continue; if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) partctrl = static_cast(ctrl.getPtr()); - else - Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } if (!partctrl) { @@ -1156,8 +1109,11 @@ namespace NifOsg partsys->getOrCreateStateSet(); } - void triCommonToGeometry(osg::Geometry *geometry, const std::vector& vertices, const std::vector& normals, const std::vector>& uvlist, const std::vector& colors, const std::vector& boundTextures, const std::string& name) + void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) { + const auto& vertices = data->vertices; + const auto& normals = data->normals; + const auto& colors = data->colors; if (!vertices.empty()) geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); if (!normals.empty()) @@ -1165,6 +1121,7 @@ namespace NifOsg if (!colors.empty()) geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); + const auto& uvlist = data->uvlist; int textureStage = 0; for (const unsigned int uvSet : boundTextures) { @@ -1181,43 +1138,53 @@ namespace NifOsg } } - void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - bool vertexColorsPresent = false; + const Nif::NiGeometryData* niGeometryData = nullptr; if (nifNode->recType == Nif::RC_NiTriShape) { const Nif::NiTriShape* triShape = static_cast(nifNode); if (!triShape->data.empty()) { const Nif::NiTriShapeData* data = triShape->data.getPtr(); - vertexColorsPresent = !data->colors.empty(); - triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name); + niGeometryData = static_cast(data); if (!data->triangles.empty()) geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), (unsigned short*)data->triangles.data())); } } - else + else if (nifNode->recType == Nif::RC_NiTriStrips) { const Nif::NiTriStrips* triStrips = static_cast(nifNode); if (!triStrips->data.empty()) { const Nif::NiTriStripsData* data = triStrips->data.getPtr(); - vertexColorsPresent = !data->colors.empty(); - triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name); + niGeometryData = static_cast(data); if (!data->strips.empty()) { - for (const std::vector& strip : data->strips) + for (const auto& strip : data->strips) { - // Can't make a triangle from less than three vertices. - if (strip.size() < 3) - continue; - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), - (unsigned short*)strip.data())); + if (strip.size() >= 3) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + (unsigned short*)strip.data())); } } } } + else if (nifNode->recType == Nif::RC_NiLines) + { + const Nif::NiLines* lines = static_cast(nifNode); + if (!lines->data.empty()) + { + const Nif::NiLinesData* data = lines->data.getPtr(); + niGeometryData = static_cast(data); + const auto& line = data->lines; + if (!line.empty()) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data())); + } + } + if (niGeometryData) + handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1225,21 +1192,16 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags); + applyDrawableProperties(parentNode, drawableProps, composite, niGeometryData && !niGeometryData->colors.empty(), animflags); } - void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); osg::ref_ptr drawable; osg::ref_ptr geom (new osg::Geometry); - triShapeToGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); - Nif::ControllerPtr ctrl; - if (nifNode->recType == Nif::RC_NiTriShape) - ctrl = static_cast(nifNode)->controller; - else - ctrl = static_cast(nifNode)->controller; - for (; !ctrl.empty(); ctrl = ctrl->next) + handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) continue; @@ -1277,12 +1239,12 @@ namespace NifOsg return morphGeom; } - void handleSkinnedTriShape(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, + void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); osg::ref_ptr geometry (new osg::Geometry); - triShapeToGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); rig->setName(nifNode->name); @@ -1813,7 +1775,7 @@ namespace NifOsg if (!matprop->controller.empty()) { hasMatCtrl = true; - handleMaterialControllers(matprop, composite, animflags); + handleMaterialControllers(matprop, composite, animflags, mat); } break; diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 4de9027b8..49a78ad5f 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -7,6 +7,7 @@ #include #include "controller.hpp" +#include "textkeymap.hpp" namespace osg { @@ -20,8 +21,6 @@ namespace Resource namespace NifOsg { - typedef std::multimap TextKeyMap; - struct TextKeyMapHolder : public osg::Object { public: diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 1b1e469bc..804a8f8ab 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -277,7 +277,7 @@ Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) , mPlacer(copy.mPlacer) , mShooter(copy.mShooter) // need a deep copy because the remainder is stored in the object - , mCounter(osg::clone(copy.mCounter.get(), osg::CopyOp::DEEP_COPY_ALL)) + , mCounter(static_cast(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL))) { } diff --git a/components/nifosg/textkeymap.hpp b/components/nifosg/textkeymap.hpp new file mode 100644 index 000000000..49e1e461e --- /dev/null +++ b/components/nifosg/textkeymap.hpp @@ -0,0 +1,87 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_TEXTKEYMAP +#define OPENMW_COMPONENTS_NIFOSG_TEXTKEYMAP + +#include +#include +#include +#include + +namespace NifOsg +{ + class TextKeyMap + { + public: + using ConstIterator = std::multimap::const_iterator; + + auto begin() const noexcept + { + return mTextKeyByTime.begin(); + } + + auto end() const noexcept + { + return mTextKeyByTime.end(); + } + + auto rbegin() const noexcept + { + return mTextKeyByTime.rbegin(); + } + + auto rend() const noexcept + { + return mTextKeyByTime.rend(); + } + + auto lowerBound(float time) const + { + return mTextKeyByTime.lower_bound(time); + } + + auto upperBound(float time) const + { + return mTextKeyByTime.upper_bound(time); + } + + void emplace(float time, std::string&& textKey) + { + const auto separator = textKey.find(": "); + if (separator != std::string::npos) + mGroups.emplace(textKey.substr(0, separator)); + + mTextKeyByTime.emplace(time, std::move(textKey)); + } + + bool empty() const noexcept + { + return mTextKeyByTime.empty(); + } + + auto findGroupStart(const std::string &groupName) const + { + return std::find_if(mTextKeyByTime.begin(), mTextKeyByTime.end(), IsGroupStart{groupName}); + } + + bool hasGroupStart(const std::string &groupName) const + { + return mGroups.count(groupName) > 0; + } + + private: + struct IsGroupStart + { + const std::string &mGroupName; + + bool operator ()(const std::multimap::value_type& value) const + { + return value.second.compare(0, mGroupName.size(), mGroupName) == 0 && + value.second.compare(mGroupName.size(), 2, ": ") == 0; + } + }; + + std::set mGroups; + std::multimap mTextKeyByTime; + }; +} + +#endif diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index cfd41f19c..24d7d04a9 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -161,13 +161,13 @@ class GenericObjectCache : public osg::Referenced } } - /** call operator()(osg::Object*) for each object in the cache. */ + /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template void call(Functor& f) { OpenThreads::ScopedLock lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->second.first.get()); + f(it->first, it->second.first.get()); } /** Get the number of objects in the cache. */ diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 61a40ee4b..8264e3b1e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -360,6 +360,7 @@ namespace Resource // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. // but findFileCallback does not support virtual files, so we can't implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); + if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); if (!result.success()) @@ -466,7 +467,7 @@ namespace Resource return options; } - osg::ref_ptr SceneManager::getTemplate(const std::string &name) + osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { std::string normalized = name; mVFS->normalizeFilename(normalized); @@ -529,7 +530,7 @@ namespace Resource optimizer.optimize(loaded, options); } - if (mIncrementalCompileOperation) + if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); else loaded->getBound(); @@ -577,7 +578,7 @@ namespace Resource osg::ref_ptr SceneManager::createInstance(const osg::Node *base) { - osg::ref_ptr cloned = osg::clone(base, SceneUtil::CopyOp()); + osg::ref_ptr cloned = static_cast(base->clone(SceneUtil::CopyOp())); // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); @@ -713,6 +714,24 @@ namespace Resource mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); + + if (mIncrementalCompileOperation) + { + OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); + osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile(); + for(osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();) + { + int refcount = (*it)->_subgraphToCompile->referenceCount(); + if ((*it)->_subgraphToCompile->asDrawable()) refcount -= 1; // ref by CompileList. + if (refcount <= 2) // ref by ObjectCache + ref by _subgraphToCompile. + { + // no other ref = not needed anymore. + it = sets.erase(it); + } + else + ++it; + } + } } void SceneManager::clearCache() diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 1c1c60a58..bbe88c9a0 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -81,7 +81,7 @@ namespace Resource /// @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); + osg::ref_ptr getTemplate(const std::string& name, bool compile=true); /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply /// return this cached object instead of creating a new one. diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 59d65e889..2dda2c2df 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -262,7 +262,6 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); #endif - osg::Vec3 pos(_statsWidth-420.f, _statsHeight-500.0f,0.0f); osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); @@ -277,6 +276,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) _switch->addChild(group, false); static const std::vector statNames({ + "FrameNumber", + "", "Compiling", "WorkQueue", "WorkThread", @@ -291,6 +292,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", @@ -302,16 +304,25 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", + "", + "Mechanics Actors", + "Mechanics Objects", + "", + "Physics Actors", + "Physics Objects", + "Physics HeightFields", }); static const auto longest = std::max_element(statNames.begin(), statNames.end(), [] (const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); - const int numLines = statNames.size(); const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; + const float statTextWidth = 7 * _characterSize + 2 * backgroundMargin; + const float statHeight = statNames.size() * _characterSize + 2 * backgroundMargin; + osg::Vec3 pos(_statsWidth - statNamesWidth - backgroundSpacing - statTextWidth, statHeight, 0.0f); group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), statNamesWidth, - numLines * _characterSize + 2 * backgroundMargin, + statHeight, backgroundColor)); osg::ref_ptr staticText = new osgText::Text; @@ -335,8 +346,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) pos.x() += statNamesWidth + backgroundSpacing; group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), - 7 * _characterSize + 2 * backgroundMargin, - numLines * _characterSize + 2 * backgroundMargin, + statTextWidth, + statHeight, backgroundColor)); osg::ref_ptr statsText = new osgText::Text; diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 0df0f4a5b..c3261515d 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -22,20 +22,11 @@ namespace SceneUtil | osg::CopyOp::DEEP_COPY_USERDATA); } - osg::StateSet* CopyOp::operator ()(const osg::StateSet* stateset) const - { - if (!stateset) - return nullptr; - if (stateset->getDataVariance() == osg::StateSet::DYNAMIC) - return osg::clone(stateset, *this); - return const_cast(stateset); - } - osg::Object* CopyOp::operator ()(const osg::Object* node) const { // We should copy node transformations when we copy node - if (const NifOsg::NodeUserData* data = dynamic_cast(node)) - return osg::clone(data, *this); + if (dynamic_cast(node)) + return static_cast(node->clone(*this)); return osg::CopyOp::operator()(node); } @@ -60,7 +51,7 @@ namespace SceneUtil if (dynamic_cast(drawable) || dynamic_cast(drawable)) { - return osg::clone(drawable, *this); + return static_cast(drawable->clone(*this)); } return osg::CopyOp::operator()(drawable); @@ -68,7 +59,7 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { - osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); + osgParticle::ParticleProcessor* cloned = static_cast(processor->clone(osg::CopyOp::DEEP_COPY_CALLBACKS)); for (const auto& oldPsNewPsPair : mOldPsToNewPs) { if (processor->getParticleSystem() == oldPsNewPsPair.first) @@ -84,7 +75,7 @@ namespace SceneUtil osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const { - osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); + osgParticle::ParticleSystem* cloned = static_cast(partsys->clone(*this)); for (const auto& processorPsPair : mProcessorToOldPs) { diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index 20788799f..cf6d79e68 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -17,7 +17,6 @@ namespace SceneUtil /// @par Defines the cloning behaviour we need: /// * Assigns updated ParticleSystem pointers on cloned emitters and programs. - /// * Creates deep copy of StateSets if they have a DYNAMIC data variance. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. /// @warning Do not use an object of this class for more than one copy operation. class CopyOp : public osg::CopyOp @@ -31,7 +30,6 @@ namespace SceneUtil virtual osg::Node* operator() (const osg::Node* node) const; virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; - virtual osg::StateSet* operator() (const osg::StateSet* stateset) const; virtual osg::Object* operator ()(const osg::Object* node) const; private: diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 01bb35c45..04fd6fb36 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -44,7 +44,7 @@ void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index cb3a1b278..98771bbb4 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -1580,7 +1581,9 @@ void MWShadowTechnique::createShaders() _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); _shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform); - + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(true); + _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 487126627..b9da2d1c8 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -18,6 +18,7 @@ #include "optimizer.hpp" +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include @@ -103,7 +105,9 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) osg::Timer_t startTick = osg::Timer::instance()->tick(); MergeGeometryVisitor mgv(this); - mgv.setTargetMaximumNumberOfVertices(10000); + mgv.setTargetMaximumNumberOfVertices(1000000); + mgv.setMergeAlphaBlending(_mergeAlphaBlending); + mgv.setViewPoint(_viewPoint); node->accept(mgv); osg::Timer_t endTick = osg::Timer::instance()->tick(); @@ -354,6 +358,21 @@ void CollectLowestTransformsVisitor::doTransform(osg::Object* obj,osg::Matrix& m { osgUtil::TransformAttributeFunctor tf(matrix); drawable->accept(tf); + + osg::Geometry *geom = drawable->asGeometry(); + osg::Vec4Array* tangents = geom ? dynamic_cast(geom->getTexCoordArray(7)) : nullptr; + if (tangents) + { + for (unsigned int i=0; isize(); ++i) + { + osg::Vec4f& itr = (*tangents)[i]; + osg::Vec3f vec3 (itr.x(), itr.y(), itr.z()); + vec3 = osg::Matrix::transform3x3(tf._im, vec3); + vec3.normalize(); + itr = osg::Vec4f(vec3.x(), vec3.y(), vec3.z(), itr.w()); + } + } + drawable->dirtyBound(); drawable->dirtyDisplayList(); @@ -585,18 +604,37 @@ void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node) traverse(node); } +bool needvbo(const osg::Geometry* geom) +{ +#if OSG_MIN_VERSION_REQUIRED(3,5,6) + return true; +#else + return geom->getUseVertexBufferObjects(); +#endif +} + +osg::Array* cloneArray(osg::Array* array, osg::VertexBufferObject*& vbo, const osg::Geometry* geom) +{ + array = static_cast(array->clone(osg::CopyOp::DEEP_COPY_ALL)); + if (!vbo && needvbo(geom)) + vbo = new osg::VertexBufferObject; + if (vbo) + array->setVertexBufferObject(vbo); + return array; +} void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) { osg::Geometry *geometry = drawable.asGeometry(); if((geometry) && (isOperationPermissibleForObject(&drawable))) { - if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) { - geometry->setVertexArray(dynamic_cast(geometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); - } - if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) { - geometry->setNormalArray(dynamic_cast(geometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); - } + osg::VertexBufferObject* vbo = nullptr; + if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) + geometry->setVertexArray(cloneArray(geometry->getVertexArray(), vbo, geometry)); + if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) + geometry->setNormalArray(cloneArray(geometry->getNormalArray(), vbo, geometry)); + if(geometry->getTexCoordArray(7) && geometry->getTexCoordArray(7)->referenceCount() > 1) // tangents + geometry->setTexCoordArray(7, cloneArray(geometry->getTexCoordArray(7), vbo, geometry)); } _drawableSet.insert(&drawable); } @@ -988,6 +1026,17 @@ struct LessGeometry } }; +struct LessGeometryViewPoint +{ + osg::Vec3f _viewPoint; + bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const + { + float len1 = (lhs->getBoundingBox().center() - _viewPoint).length2(); + float len2 = (rhs->getBoundingBox().center() - _viewPoint).length2(); + return len2 < len1; + } +}; + struct LessGeometryPrimitiveType { bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const @@ -1055,16 +1104,16 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { _stateSetStack.push_back(stateSet); - checkAllowedToMerge(); + checkAlphaBlendingActive(); } void Optimizer::MergeGeometryVisitor::popStateSet() { _stateSetStack.pop_back(); - checkAllowedToMerge(); + checkAlphaBlendingActive(); } -void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() +void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive() { int renderingHint = 0; bool override = false; @@ -1080,7 +1129,7 @@ void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() override = true; } // Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break. - _allowedToMerge = renderingHint != osg::StateSet::TRANSPARENT_BIN; + _alphaBlendingActive = renderingHint == osg::StateSet::TRANSPARENT_BIN; } void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) @@ -1088,7 +1137,7 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) if (group.getStateSet()) pushStateSet(group.getStateSet()); - if (_allowedToMerge) + if (!_alphaBlendingActive || _mergeAlphaBlending) mergeGroup(group); traverse(group); @@ -1097,6 +1146,30 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) popStateSet(); } +osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps, osg::ElementBufferObject*& ebo, const osg::Geometry* geom) +{ + if (ps->referenceCount() <= 1) + return ps; + ps = static_cast(ps->clone(osg::CopyOp::DEEP_COPY_ALL)); + + osg::DrawElements* drawElements = ps->getDrawElements(); + if (!drawElements) return ps; + + if (!ebo && needvbo(geom)) + ebo = new osg::ElementBufferObject; + if (ebo) + drawElements->setElementBufferObject(ebo); + + return ps; +} + +bool containsSharedPrimitives(const osg::Geometry* geom) +{ + for (unsigned int i=0; igetNumPrimitiveSets(); ++i) + if (geom->getPrimitiveSet(i)->referenceCount() > 1) return true; + return false; +} + bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (!isOperationPermissibleForObject(&group)) return false; @@ -1120,7 +1193,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) osg::Geometry* geom = child->asGeometry(); if (geom) { - if (!geometryContainsSharedArrays(*geom) && + if ( geom->getDataVariance()!=osg::Object::DYNAMIC && isOperationPermissibleForObject(geom)) { @@ -1254,6 +1327,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) DuplicateList& duplicateList = *mitr; if (!duplicateList.empty()) { + if (_alphaBlendingActive) + { + LessGeometryViewPoint lgvp; + lgvp._viewPoint = _viewPoint; + std::sort(duplicateList.begin(), duplicateList.end(), lgvp); + } DuplicateList::iterator ditr = duplicateList.begin(); osg::ref_ptr lhs = *ditr++; group.addChild(lhs.get()); @@ -1278,6 +1357,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); + osg::ElementBufferObject* ebo = nullptr; if (geom) { osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); @@ -1290,10 +1370,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (prim->getNumIndices()==3) { + prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::TRIANGLES); } else if (prim->getNumIndices()==4) { + prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::QUADS); } } @@ -1308,6 +1390,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); + osg::ElementBufferObject* ebo = nullptr; if (geom) { if (geom->getNumPrimitiveSets()>0 && @@ -1320,6 +1403,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) #if 1 bool doneCombine = false; + std::set toremove; + osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); unsigned int lhsNo=0; unsigned int rhsNo=1; @@ -1348,6 +1433,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (combine) { + lhs = clonePrimitive(lhs, ebo, geom); + primitives[lhsNo] = lhs; switch(lhs->getType()) { @@ -1375,7 +1462,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (combine) { // make this primitive set as invalid and needing cleaning up. - rhs->setMode(0xffffff); + toremove.insert(rhs); doneCombine = true; ++rhsNo; } @@ -1390,7 +1477,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (doneCombine) { // now need to clean up primitiveset so it no longer contains the rhs combined primitives. - // first swap with a empty primitiveSet to empty it completely. osg::Geometry::PrimitiveSetList oldPrimitives; primitives.swap(oldPrimitives); @@ -1400,7 +1486,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) pitr != oldPrimitives.end(); ++pitr) { - if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr); + if (!toremove.count(*pitr)) primitives.push_back(*pitr); } } #endif @@ -1467,6 +1553,18 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) } } #endif + if (doneCombine && !geom->containsSharedArrays() && !containsSharedPrimitives(geom)) + { + // prefer to use vbo for merged geometries as vbo uses less memory than display lists. + geom->setUseVertexBufferObjects(true); + geom->setUseDisplayList(false); + } + if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) + { + osg::Depth* d = new osg::Depth; + d->setWriteMask(0); + geom->getOrCreateStateSet()->setAttribute(d); + } } } @@ -1479,34 +1577,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) return false; } -bool Optimizer::MergeGeometryVisitor::geometryContainsSharedArrays(osg::Geometry& geom) -{ - if (geom.getVertexArray() && geom.getVertexArray()->referenceCount()>1) return true; - if (geom.getNormalArray() && geom.getNormalArray()->referenceCount()>1) return true; - if (geom.getColorArray() && geom.getColorArray()->referenceCount()>1) return true; - if (geom.getSecondaryColorArray() && geom.getSecondaryColorArray()->referenceCount()>1) return true; - if (geom.getFogCoordArray() && geom.getFogCoordArray()->referenceCount()>1) return true; - - - for(unsigned int unit=0;unitreferenceCount()>1) return true; - } - - // shift the indices of the incoming primitives to account for the pre existing geometry. - for(osg::Geometry::PrimitiveSetList::iterator primItr=geom.getPrimitiveSetList().begin(); - primItr!=geom.getPrimitiveSetList().end(); - ++primItr) - { - if ((*primItr)->referenceCount()>1) return true; - } - - - return false; -} - - class MergeArrayVisitor : public osg::ArrayVisitor { protected: @@ -1574,14 +1644,14 @@ class MergeArrayVisitor : public osg::ArrayVisitor bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) { - MergeArrayVisitor merger; - + osg::VertexBufferObject* vbo = nullptr; unsigned int base = 0; if (lhs.getVertexArray() && rhs.getVertexArray()) { - base = lhs.getVertexArray()->getNumElements(); + if (lhs.getVertexArray()->referenceCount() > 1) + lhs.setVertexArray(cloneArray(lhs.getVertexArray(), vbo, &lhs)); if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray())) { OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getNormalArray()->referenceCount() > 1) + lhs.setNormalArray(cloneArray(lhs.getNormalArray(), vbo, &lhs)); if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray())) { OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getColorArray()->referenceCount() > 1) + lhs.setColorArray(cloneArray(lhs.getColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getColorArray(),rhs.getColorArray())) { OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getSecondaryColorArray()->referenceCount() > 1) + lhs.setSecondaryColorArray(cloneArray(lhs.getSecondaryColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray())) { OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { + if (lhs.getFogCoordArray()->referenceCount() > 1) + lhs.setFogCoordArray(cloneArray(lhs.getFogCoordArray(), vbo, &lhs)); if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray())) { OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <referenceCount() > 1) + lhs.setTexCoordArray(unit, cloneArray(lhs.getTexCoordArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit))) { OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <referenceCount() > 1) + lhs.setVertexAttribArray(unit, cloneArray(lhs.getVertexAttribArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit))) { OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; @@ -1691,13 +1780,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom { // must promote to a DrawElementsUShort osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive, ebo, &lhs); + (*primItr)->offsetIndices(base); } } break; @@ -1716,13 +1811,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom { // must promote to a DrawElementsUInt osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); + if (needvbo(&lhs)) + { + if (!ebo) ebo = new osg::ElementBufferObject; + new_primitive->setElementBufferObject(ebo); + } std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive, ebo, &lhs); + (*primItr)->offsetIndices(base); } } break; @@ -1731,7 +1832,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): default: - primitive->offsetIndices(base); + (*primItr) = clonePrimitive(primitive, ebo, &lhs); + (*primItr)->offsetIndices(base); break; } } @@ -1744,6 +1846,10 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom lhs.dirtyBound(); lhs.dirtyDisplayList(); + if (osg::UserDataContainer* rhsUserData = rhs.getUserDataContainer()) + for (unsigned int i=0; igetNumUserObjects(); ++i) + lhs.getOrCreateUserDataContainer()->addUserObject(rhsUserData->getUserObject(i)); + return true; } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 6dd4394d1..d7c83e898 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -65,7 +65,7 @@ class Optimizer public: - Optimizer() {} + Optimizer() : _mergeAlphaBlending(false) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -118,6 +118,9 @@ class Optimizer STATIC_OBJECT_DETECTION }; + void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } + void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } + /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ void reset(); @@ -252,6 +255,9 @@ class Optimizer typedef std::map PermissibleOptimizationsMap; PermissibleOptimizationsMap _permissibleOptimizationsMap; + osg::Vec3f _viewPoint; + bool _mergeAlphaBlending; + public: /** Flatten Static Transform nodes by applying their transform to the @@ -371,7 +377,16 @@ class Optimizer /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), - _targetMaximumNumberOfVertices(10000), _allowedToMerge(true) {} + _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {} + + void setMergeAlphaBlending(bool merge) + { + _mergeAlphaBlending = merge; + } + void setViewPoint(const osg::Vec3f& viewPoint) + { + _viewPoint = viewPoint; + } void setTargetMaximumNumberOfVertices(unsigned int num) { @@ -385,15 +400,13 @@ class Optimizer void pushStateSet(osg::StateSet* stateSet); void popStateSet(); - void checkAllowedToMerge(); + void checkAlphaBlendingActive(); virtual void apply(osg::Group& group); virtual void apply(osg::Billboard&) { /* don't do anything*/ } bool mergeGroup(osg::Group& group); - static bool geometryContainsSharedArrays(osg::Geometry& geom); - static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs); static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs); @@ -406,7 +419,9 @@ class Optimizer unsigned int _targetMaximumNumberOfVertices; std::vector _stateSetStack; - bool _allowedToMerge; + bool _alphaBlendingActive; + bool _mergeAlphaBlending; + osg::Vec3f _viewPoint; }; }; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 97c8e1d39..b9201fdf6 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -77,7 +77,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); @@ -86,7 +86,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) if (const osg::Array* normals = from.getNormalArray()) { - osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr normalArray = static_cast(normals->clone(osg::CopyOp::DEEP_COPY_ALL)); if (normalArray) { normalArray->setVertexBufferObject(vbo); @@ -97,7 +97,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) { mSourceTangents = tangents; - osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); + osg::ref_ptr tangentArray = static_cast(tangents->clone(osg::CopyOp::DEEP_COPY_ALL)); tangentArray->setVertexBufferObject(vbo); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); } @@ -106,7 +106,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) } } -osg::ref_ptr RigGeometry::getSourceGeometry() +osg::ref_ptr RigGeometry::getSourceGeometry() const { return mSourceGeometry; } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index a393530ec..801c172b3 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -44,7 +44,7 @@ namespace SceneUtil /// @note The source geometry will not be modified. void setSourceGeometry(osg::ref_ptr sourceGeom); - osg::ref_ptr getSourceGeometry(); + osg::ref_ptr getSourceGeometry() const; virtual void accept(osg::NodeVisitor &nv); virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 51516bfac..d1490fee4 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -2,6 +2,7 @@ #include +#include #include namespace SceneUtil @@ -38,9 +39,11 @@ namespace SceneUtil } mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); - if (Settings::Manager::getBool("compute tight scene bounds", "Shadows")) + + std::string computeSceneBounds = Settings::Manager::getString("compute scene bounds", "Shadows"); + if (Misc::StringUtils::lowerCase(computeSceneBounds) == "primitives") mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); - else + else if (Misc::StringUtils::lowerCase(computeSceneBounds) == "bounds") mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 121cdaca6..08418f331 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -2,30 +2,38 @@ #include #include +#include namespace SceneUtil { void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) { + bool isCullVisitor = nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (!mStateSets[0]) { - // first time setup - osg::StateSet* src = node->getOrCreateStateSet(); - for (int i=0; i<2; ++i) // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first - // This can be done conveniently in user implementations of the setDefaults() method + for (int i=0; i<2; ++i) { - mStateSets[i] = new osg::StateSet(*src, osg::CopyOp::SHALLOW_COPY); + if (!isCullVisitor) + mStateSets[i] = new osg::StateSet(*node->getOrCreateStateSet(), osg::CopyOp::SHALLOW_COPY); // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first in setDefaults + else + mStateSets[i] = new osg::StateSet; setDefaults(mStateSets[i]); } } osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2]; - node->setStateSet(stateset); - apply(stateset, nv); + if (!isCullVisitor) + node->setStateSet(stateset); + else + static_cast(nv)->pushStateSet(stateset); + traverse(node, nv); + + if (isCullVisitor) + static_cast(nv)->popStateSet(); } void StateSetUpdater::reset() diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 51398844c..d12316fb2 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -13,7 +13,7 @@ namespace SceneUtil /// traversals run in parallel can yield up to 200% framerates. /// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. - /// @par Must be set as UpdateCallback on a Node. + /// @par Must be set as UpdateCallback or CullCallback on a Node. If set as a CullCallback, the StateSetUpdater operates on an empty StateSet, otherwise it operates on a clone of the node's existing StateSet. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. class StateSetUpdater : public osg::NodeCallback diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 0ca5e4d3e..3f8ed8aaf 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -2,69 +2,55 @@ #include +#include + namespace SceneUtil { void WorkItem::waitTillDone() { - if (mDone > 0) + if (mDone) return; - OpenThreads::ScopedLock lock(mMutex); - while (mDone == 0) + std::unique_lock lock(mMutex); + while (!mDone) { - mCondition.wait(&mMutex); + mCondition.wait(lock); } } void WorkItem::signalDone() { { - OpenThreads::ScopedLock lock(mMutex); - mDone.exchange(1); + std::unique_lock lock(mMutex); + mDone = true; } - mCondition.broadcast(); -} - -WorkItem::WorkItem() -{ -} - -WorkItem::~WorkItem() -{ + mCondition.notify_all(); } bool WorkItem::isDone() const { - return (mDone > 0); + return mDone; } WorkQueue::WorkQueue(int workerThreads) : mIsReleased(false) { for (int i=0; istartThread(); - } + mThreads.emplace_back(std::make_unique(*this)); } WorkQueue::~WorkQueue() { { - OpenThreads::ScopedLock lock(mMutex); + std::unique_lock lock(mMutex); while (!mQueue.empty()) mQueue.pop_back(); mIsReleased = true; - mCondition.broadcast(); + mCondition.notify_all(); } - for (unsigned int i=0; ijoin(); - delete mThreads[i]; - } + mThreads.clear(); } void WorkQueue::addWorkItem(osg::ref_ptr item, bool front) @@ -75,20 +61,20 @@ void WorkQueue::addWorkItem(osg::ref_ptr item, bool front) return; } - OpenThreads::ScopedLock lock(mMutex); + std::unique_lock lock(mMutex); if (front) mQueue.push_front(item); else mQueue.push_back(item); - mCondition.signal(); + mCondition.notify_one(); } osg::ref_ptr WorkQueue::removeWorkItem() { - OpenThreads::ScopedLock lock(mMutex); + std::unique_lock lock(mMutex); while (mQueue.empty() && !mIsReleased) { - mCondition.wait(&mMutex); + mCondition.wait(lock); } if (!mQueue.empty()) { @@ -102,25 +88,26 @@ osg::ref_ptr WorkQueue::removeWorkItem() unsigned int WorkQueue::getNumItems() const { - OpenThreads::ScopedLock lock(mMutex); + std::unique_lock lock(mMutex); return mQueue.size(); } unsigned int WorkQueue::getNumActiveThreads() const { - unsigned int count = 0; - for (unsigned int i=0; iisActive()) - ++count; - } - return count; + return std::accumulate(mThreads.begin(), mThreads.end(), 0u, + [] (auto r, const auto& t) { return r + t->isActive(); }); } -WorkThread::WorkThread(WorkQueue *workQueue) - : mWorkQueue(workQueue) +WorkThread::WorkThread(WorkQueue& workQueue) + : mWorkQueue(&workQueue) , mActive(false) + , mThread([this] { run(); }) +{ +} + +WorkThread::~WorkThread() { + mThread.join(); } void WorkThread::run() diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 0b16db0e7..5b51c59e5 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -1,16 +1,14 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H #define OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H -#include -#include -#include -#include - #include #include #include #include +#include +#include +#include namespace SceneUtil { @@ -18,9 +16,6 @@ namespace SceneUtil class WorkItem : public osg::Referenced { public: - WorkItem(); - virtual ~WorkItem(); - /// Override in a derived WorkItem to perform actual work. virtual void doWork() {} @@ -35,10 +30,10 @@ namespace SceneUtil /// Set abort flag in order to return from doWork() as soon as possible. May not be respected by all WorkItems. virtual void abort() {} - protected: - OpenThreads::Atomic mDone; - OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; + private: + std::atomic_bool mDone {false}; + std::mutex mMutex; + std::condition_variable mCondition; }; class WorkThread; @@ -70,25 +65,28 @@ namespace SceneUtil bool mIsReleased; std::deque > mQueue; - mutable OpenThreads::Mutex mMutex; - OpenThreads::Condition mCondition; + mutable std::mutex mMutex; + std::condition_variable mCondition; - std::vector mThreads; + std::vector> mThreads; }; /// Internally used by WorkQueue. - class WorkThread : public OpenThreads::Thread + class WorkThread { public: - WorkThread(WorkQueue* workQueue); + WorkThread(WorkQueue& workQueue); - virtual void run(); + ~WorkThread(); bool isActive() const; private: WorkQueue* mWorkQueue; std::atomic mActive; + std::thread mThread; + + void run(); }; diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 6eabadf92..47c567f54 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -1,5 +1,6 @@ #include "cellborder.hpp" +#include #include #include #include @@ -64,6 +65,9 @@ void CellBorder::createCellBorderGeometry(int x, int y) borderGeode->addDrawable(border.get()); osg::StateSet *stateSet = borderGeode->getOrCreateStateSet(); + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateSet->setAttribute(material); osg::PolygonMode* polygonmode = new osg::PolygonMode; polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 95c1ca491..87966b47c 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -4,13 +4,13 @@ #include #include +#include #include #include #include -#include #include #include "terraindrawable.hpp" @@ -28,14 +28,19 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mSceneManager(sceneMgr) , mTextureManager(textureManager) , mCompositeMapRenderer(renderer) + , mNodeMask(0) , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) { - + mMultiPassRoot = new osg::StateSet; + mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); @@ -43,7 +48,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f &cen return obj->asNode(); else { - osg::ref_ptr node = createChunk(size, center, lod, lodFlags); + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -161,12 +166,8 @@ std::vector > ChunkManager::createPasses(float chunk return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } -osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) { - osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); - osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); - transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); osg::ref_ptr colors (new osg::Vec4ubArray); @@ -201,6 +202,8 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->createClusterCullingCallback(); + geometry->setStateSet(mMultiPassRoot); + if (useCompositeMap) { osg::ref_ptr compositeMap = new CompositeMap; @@ -224,16 +227,15 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); } - transform->addChild(geometry); - transform->getBound(); - geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); - if (mSceneManager->getIncrementalCompileOperation()) + if (compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } - return transform; + geometry->setNodeMask(mNodeMask); + + return geometry; } } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index be83e158c..02a782c62 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -6,6 +6,7 @@ #include #include "buffercache.hpp" +#include "quadtreeworld.hpp" namespace osg { @@ -29,17 +30,20 @@ namespace Terrain typedef std::tuple ChunkId; // Center, Lod, Lod Flags /// @brief Handles loading and caching of terrain chunks - class ChunkManager : public Resource::GenericResourceManager + class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } + void setNodeMask(unsigned int mask) { mNodeMask = mask; } + virtual unsigned int getNodeMask() override { return mNodeMask; } + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void clearCache() override; @@ -47,7 +51,7 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile); osg::ref_ptr createCompositeMapRTT(); @@ -61,6 +65,10 @@ namespace Terrain CompositeMapRenderer* mCompositeMapRenderer; BufferCache mBufferCache; + osg::ref_ptr mMultiPassRoot; + + unsigned int mNodeMask; + unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fd385e793..e662f4439 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -183,17 +183,20 @@ namespace Terrain osg::ref_ptr stateset (new osg::StateSet); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - - if (!firstLayer) + if (!blendmaps.empty()) { - stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); - } - else - { - stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setRenderBinDetails(passIndex++, "RenderBin"); + if (!firstLayer) + { + stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); + } + else + { + stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); + } } int texunit = 0; @@ -268,8 +271,6 @@ namespace Terrain } - stateset->setRenderBinDetails(passIndex++, "RenderBin"); - passes.push_back(stateset); } return passes; diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index a28554be9..7baea45c8 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -108,71 +108,26 @@ void QuadTreeNode::initNeighbours() getChild(i)->initNeighbours(); } -void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist) +void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback) { if (!hasValidBounds()) return; - - float dist = distance(viewPoint); - if (dist > maxDist) + LodCallback::ReturnValue lodResult = lodCallback->isSufficientDetail(this, distance(viewPoint)); + if (lodResult == LodCallback::StopTraversal) return; - - bool stopTraversal = (lodCallback->isSufficientDetail(this, dist)) || !getNumChildren(); - - if (stopTraversal) - vd->add(this); - else + else if (lodResult == LodCallback::Deeper && getNumChildren()) { for (unsigned int i=0; itraverseNodes(vd, viewPoint, lodCallback, maxDist); + getChild(i)->traverseNodes(vd, viewPoint, lodCallback); } -} - -void QuadTreeNode::traverseTo(ViewData* vd, float size, const osg::Vec2f& center) -{ - if (!hasValidBounds()) - return; - - if (getCenter().x() + getSize()/2.f <= center.x() - size/2.f - || getCenter().x() - getSize()/2.f >= center.x() + size/2.f - || getCenter().y() + getSize()/2.f <= center.y() - size/2.f - || getCenter().y() - getSize()/2.f >= center.y() + size/2.f) - return; - - bool stopTraversal = (getSize() == size); - - if (stopTraversal) - vd->add(this); else - { - for (unsigned int i=0; itraverseTo(vd, size, center); - } -} - -void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector& intersector) -{ - if (!hasValidBounds()) - return; - - if (!intersector.intersectAndClip(getBoundingBox())) - return; - - if (getNumChildren() == 0) vd->add(this); - else - { - for (unsigned int i=0; iintersect(vd, intersector); - } } void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) { mBoundingBox = boundingBox; mValidBounds = boundingBox.valid(); - dirtyBound(); - getBound(); } const osg::BoundingBox &QuadTreeNode::getBoundingBox() const @@ -180,11 +135,6 @@ const osg::BoundingBox &QuadTreeNode::getBoundingBox() const return mBoundingBox; } -osg::BoundingSphere QuadTreeNode::computeBound() const -{ - return osg::BoundingSphere(mBoundingBox); -} - float QuadTreeNode::getSize() const { return mSize; diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 4adbc6025..a309d91fc 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -2,39 +2,12 @@ #define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #include -#include #include "defs.hpp" namespace Terrain { - class TerrainLineIntersector : public osgUtil::LineSegmentIntersector - { - public: - TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector, osg::Matrix& matrix) : - osgUtil::LineSegmentIntersector(intersector->getStart() * matrix, intersector->getEnd() * matrix) - { - setPrecisionHint(intersector->getPrecisionHint()); - _intersectionLimit = intersector->getIntersectionLimit(); - _parent = intersector; - } - - TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector) : - osgUtil::LineSegmentIntersector(intersector->getStart(), intersector->getEnd()) - { - setPrecisionHint(intersector->getPrecisionHint()); - _intersectionLimit = intersector->getIntersectionLimit(); - _parent = intersector; - } - - bool intersectAndClip(const osg::BoundingBox& bbInput) - { - osg::Vec3d s(_start), e(_end); - return osgUtil::LineSegmentIntersector::intersectAndClip(s, e, bbInput); - } - }; - enum ChildDirection { NW = 0, @@ -50,10 +23,15 @@ namespace Terrain public: virtual ~LodCallback() {} - virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; + enum ReturnValue + { + Deeper, + StopTraversal, + StopTraversalAndUse + }; + virtual ReturnValue isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; - class ViewDataMap; class ViewData; class QuadTreeNode : public osg::Group @@ -91,8 +69,6 @@ namespace Terrain const osg::BoundingBox& getBoundingBox() const; bool hasValidBounds() const { return mValidBounds; } - virtual osg::BoundingSphere computeBound() const; - /// size in cell coordinates float getSize() const; @@ -100,13 +76,7 @@ namespace Terrain const osg::Vec2f& getCenter() const; /// Traverse nodes according to LOD selection. - void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist); - - /// Traverse to a specific node and add only that node. - void traverseTo(ViewData* vd, float size, const osg::Vec2f& center); - - /// Adds all leaf nodes which intersect the line from start to end - void intersect(ViewData* vd, TerrainLineIntersector& intersector); + void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback); private: QuadTreeNode* mParent; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c43a9a21b..e09433061 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -52,25 +53,45 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float factor, float minSize) + DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid) : mFactor(factor) , mMinSize(minSize) + , mViewDistance(viewDistance) + , mActiveGrid(grid) { } - virtual bool isSufficientDetail(QuadTreeNode* node, float dist) + virtual ReturnValue isSufficientDetail(QuadTreeNode* node, float dist) { + const osg::Vec2f& center = node->getCenter(); + bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w()); + if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded + return StopTraversal; + if (node->getSize()>1) + { + float halfSize = node->getSize()/2; + osg::Vec4i nodeBounds (static_cast(center.x() - halfSize), static_cast(center.y() - halfSize), static_cast(center.x() + halfSize), static_cast(center.y() + halfSize)); + bool intersects = (std::max(nodeBounds.x(), mActiveGrid.x()) < std::min(nodeBounds.z(), mActiveGrid.z()) && std::max(nodeBounds.y(), mActiveGrid.y()) < std::min(nodeBounds.w(), mActiveGrid.w())); + // to prevent making chunks who will cross the activegrid border + if (intersects) + return Deeper; + } + int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); - return nativeLodLevel <= lodLevel; + return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper; } private: float mFactor; float mMinSize; + float mViewDistance; + osg::Vec4i mActiveGrid; }; +const float MIN_SIZE = 1/8.f; + class RootNode : public QuadTreeNode { public: @@ -125,6 +146,8 @@ public: addChildren(mRootNode); mRootNode->initNeighbours(); + float cellWorldSize = mStorage->getCellWorldSize(); + mRootNode->setInitialBound(osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX*cellWorldSize, mMinY*cellWorldSize, 0), osg::Vec3(mMaxX*cellWorldSize, mMaxY*cellWorldSize, 0)))); } void addChildren(QuadTreeNode* parent) @@ -231,11 +254,11 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); + mChunkManagers.push_back(mChunkManager.get()); } QuadTreeWorld::~QuadTreeWorld() { - mViewDataMap->clear(); } /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. @@ -261,7 +284,7 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewData* vd) +unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -289,7 +312,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewD return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, ChunkManager* chunkManager) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; @@ -308,7 +331,20 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C } if (!entry.mRenderingNode) - entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); + { + osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; + pat->setPosition(osg::Vec3f(entry.mNode->getCenter().x()*cellWorldSize, entry.mNode->getCenter().y()*cellWorldSize, 0.f)); + + const osg::Vec2f& center = entry.mNode->getCenter(); + bool activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w()); + + for (QuadTreeWorld::ChunkManager* m : chunkManagers) + { + osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); + if (n) pat->addChild(n); + } + entry.mRenderingNode = pat; + } } void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) @@ -382,71 +418,29 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) return; } + osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; - ViewData* vd = nullptr; - if (isCullVisitor) - vd = mViewDataMap->getViewData(static_cast(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate); - else - { - static ViewData sIntersectionViewData; - vd = &sIntersectionViewData; - } + ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); - if (isCullVisitor) - { - osgUtil::CullVisitor* cv = static_cast(&nv); - - osg::UserDataContainer* udc = cv->getCurrentCamera()->getUserDataContainer(); - if (udc && udc->getNumDescriptions() >= 2 && udc->getDescriptions()[0] == "NoTerrainLod") - { - std::istringstream stream(udc->getDescriptions()[1]); - int x,y; - stream >> x; - stream >> y; - mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5,y+0.5)); - } - else - mRootNode->traverseNodes(vd, cv->getViewPoint(), mLodCallback, mViewDistance); - } - else - { - osgUtil::IntersectionVisitor* iv = static_cast(&nv); - osgUtil::LineSegmentIntersector* lineIntersector = dynamic_cast(iv->getIntersector()); - if (!lineIntersector) - throw std::runtime_error("Cannot update QuadTreeWorld: node visitor is not LineSegmentIntersector"); - - if (lineIntersector->getCoordinateFrame() == osgUtil::Intersector::CoordinateFrame::MODEL && iv->getModelMatrix() == 0) - { - TerrainLineIntersector terrainIntersector(lineIntersector); - mRootNode->intersect(vd, terrainIntersector); - } - else - { - osg::Matrix matrix(lineIntersector->getTransformation(*iv, lineIntersector->getCoordinateFrame())); - TerrainLineIntersector terrainIntersector(lineIntersector, matrix); - mRootNode->intersect(vd, terrainIntersector); - } - } + DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } + const float cellWorldSize = mStorage->getCellWorldSize(); + for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); - + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false); entry.mRenderingNode->accept(nv); } if (isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); - if (!isCullVisitor) - vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. - vd->markUnchanged(); double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0; @@ -463,9 +457,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - const float minSize = 1/8.f; - mLodCallback = new DefaultLodCallback(mLodFactor, minSize); - QuadTreeBuilder builder(mStorage, minSize); + QuadTreeBuilder builder(mStorage, MIN_SIZE); builder.build(); mRootNode = builder.getRootNode(); @@ -487,48 +479,38 @@ void QuadTreeWorld::enable(bool enabled) mRootNode->setNodeMask(enabled ? ~0 : 0); } -void QuadTreeWorld::cacheCell(View *view, int x, int y) -{ - ensureQuadTreeBuilt(); - ViewData* vd = static_cast(view); - mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f)); - - for (unsigned int i=0; igetNumEntries(); ++i) - { - ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); - } -} - View* QuadTreeWorld::createView() { - return new ViewData; + return mViewDataMap->createIndependentView(); } -void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, std::atomic &abort) +void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, std::atomic &progress, int& progressTotal) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); - mRootNode->traverseNodes(vd, viewPoint, mLodCallback, mViewDistance); + vd->setActiveGrid(grid); + DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + if (!progressTotal) + for (unsigned int i=0; igetNumEntries(); ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + + const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true); + progress += entry.mNode->getSize(); } vd->markUnchanged(); } -void QuadTreeWorld::storeView(const View* view, double referenceTime) +bool QuadTreeWorld::storeView(const View* view, double referenceTime) { - osg::ref_ptr dummy = new osg::DummyObject; - const ViewData* vd = static_cast(view); - bool needsUpdate = false; - ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), needsUpdate); - stored->copyFrom(*vd); - stored->setLastUsageTimeStamp(referenceTime); + return mViewDataMap->storeView(static_cast(view), referenceTime); } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) @@ -556,5 +538,15 @@ void QuadTreeWorld::unloadCell(int x, int y) World::unloadCell(x,y); } +void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) +{ + mChunkManagers.push_back(m); + mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); +} + +void QuadTreeWorld::rebuildViews() +{ + mViewDataMap->rebuildViews(); +} } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index bcb671ee1..47cf46138 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -15,7 +15,6 @@ namespace Terrain { class RootNode; class ViewDataMap; - class LodCallback; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) @@ -27,21 +26,31 @@ namespace Terrain void accept(osg::NodeVisitor& nv); - virtual void enable(bool enabled); + void enable(bool enabled) override; - virtual void setViewDistance(float distance) { mViewDistance = distance; } + void setViewDistance(float distance) override { mViewDistance = distance; } - void cacheCell(View *view, int x, int y); + void cacheCell(View *view, int x, int y) override {} /// @note Not thread safe. - virtual void loadCell(int x, int y); + void loadCell(int x, int y) override; /// @note Not thread safe. - virtual void unloadCell(int x, int y); + void unloadCell(int x, int y) override; - View* createView(); - void preload(View* view, const osg::Vec3f& eyePoint, std::atomic& abort); - void storeView(const View* view, double referenceTime); + View* createView() override; + void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) override; + bool storeView(const View* view, double referenceTime) override; + void rebuildViews() override; - void reportStats(unsigned int frameNumber, osg::Stats* stats); + void reportStats(unsigned int frameNumber, osg::Stats* stats) override; + + class ChunkManager + { + public: + virtual ~ChunkManager(){} + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0; + virtual unsigned int getNodeMask() { return 0; } + }; + void addChunkManager(ChunkManager*); private: void ensureQuadTreeBuilt(); @@ -49,7 +58,8 @@ namespace Terrain osg::ref_ptr mRootNode; osg::ref_ptr mViewDataMap; - osg::ref_ptr mLodCallback; + + std::vector mChunkManagers; OpenThreads::Mutex mQuadTreeMutex; bool mQuadTreeBuilt; diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 9593687cf..0d82be4ff 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -102,6 +102,10 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); + osg::StateSet* stateset = getStateSet(); + if (stateset) + cv->pushStateSet(stateset); + for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) { cv->pushStateSet(*it); @@ -109,6 +113,8 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) cv->popStateSet(); } + if (stateset) + cv->popStateSet(); if (pushedLight) cv->popStateSet(); } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index a0e5e4718..5f99cd97e 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -5,9 +5,10 @@ #include #include +#include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" - +#include "storage.hpp" namespace Terrain { @@ -57,12 +58,17 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu } else { - osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0); + osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true); if (!node) return nullptr; + + const float cellWorldSize = mStorage->getCellWorldSize(); + osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; + pat->setPosition(osg::Vec3f(chunkCenter.x()*cellWorldSize, chunkCenter.y()*cellWorldSize, 0.f)); + pat->addChild(node); if (parent) - parent->addChild(node); - return node; + parent->addChild(pat); + return pat; } } diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index b901fa159..c28b13f1d 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -25,7 +25,7 @@ struct UpdateTextureFilteringFunctor } Resource::SceneManager* mSceneManager; - void operator()(osg::Object* obj) + void operator()(std::string, osg::Object* obj) { mSceneManager->applyFilterSettings(static_cast(obj)); } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index d07a0e356..c24252b7d 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -1,5 +1,7 @@ #include "viewdata.hpp" +#include "quadtreenode.hpp" + namespace Terrain { @@ -8,6 +10,7 @@ ViewData::ViewData() , mLastUsageTimeStamp(0.0) , mChanged(false) , mHasViewPoint(false) + , mWorldUpdateRevision(0) { } @@ -24,6 +27,8 @@ void ViewData::copyFrom(const ViewData& other) mChanged = other.mChanged; mHasViewPoint = other.mHasViewPoint; mViewPoint = other.mViewPoint; + mActiveGrid = other.mActiveGrid; + mWorldUpdateRevision = other.mWorldUpdateRevision; } void ViewData::add(QuadTreeNode *node) @@ -90,7 +95,12 @@ void ViewData::clear() mHasViewPoint = false; } -bool ViewData::contains(QuadTreeNode *node) +bool ViewData::suitableToUse(const osg::Vec4i &activeGrid) const +{ + return hasViewPoint() && activeGrid == mActiveGrid && getNumEntries(); +} + +bool ViewData::contains(QuadTreeNode *node) const { for (unsigned int i=0; ihasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist; -} - -ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, bool& needsUpdate) +ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate) { - Map::const_iterator found = mViews.find(viewer); + ViewerMap::const_iterator found = mViewers.find(viewer); ViewData* vd = nullptr; - if (found == mViews.end()) + if (found == mViewers.end()) { vd = createOrReuseView(); - mViews[viewer] = vd; + mViewers[viewer] = vd; } else vd = found->second; + needsUpdate = false; - if (!suitable(vd, viewPoint, mReuseDistance)) + if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other) + float shortestDist = std::numeric_limits::max(); + const ViewData* mostSuitableView = nullptr; + for (const ViewData* other : mUsedViews) { - if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries()) + if (other->suitableToUse(activeGrid) && other->getWorldUpdateRevision() >= mWorldUpdateRevision) { - vd->copyFrom(*other->second); - needsUpdate = false; - return vd; + float dist = (viewPoint-other->getViewPoint()).length2(); + if (dist < shortestDist) + { + shortestDist = dist; + mostSuitableView = other; + } } } + if (mostSuitableView && mostSuitableView != vd) + { + vd->copyFrom(*mostSuitableView); + return vd; + } + } + if (!vd->suitableToUse(activeGrid)) + { vd->setViewPoint(viewPoint); + vd->setActiveGrid(activeGrid); needsUpdate = true; } - else - needsUpdate = false; - return vd; } +bool ViewDataMap::storeView(const ViewData* view, double referenceTime) +{ + if (view->getWorldUpdateRevision() < mWorldUpdateRevision) + return false; + ViewData* store = createOrReuseView(); + store->copyFrom(*view); + store->setLastUsageTimeStamp(referenceTime); + return true; +} + ViewData *ViewDataMap::createOrReuseView() { + ViewData* vd = nullptr; if (mUnusedViews.size()) { - ViewData* vd = mUnusedViews.front(); + vd = mUnusedViews.front(); mUnusedViews.pop_front(); - return vd; } else { mViewVector.push_back(ViewData()); - return &mViewVector.back(); + vd = &mViewVector.back(); } + mUsedViews.push_back(vd); + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; +} + +ViewData *ViewDataMap::createIndependentView() const +{ + ViewData* vd = new ViewData; + vd->setWorldUpdateRevision(mWorldUpdateRevision); + return vd; } void ViewDataMap::clearUnusedViews(double referenceTime) { - for (Map::iterator it = mViews.begin(); it != mViews.end(); ) + for (ViewerMap::iterator it = mViewers.begin(); it != mViewers.end(); ) + { + if (it->second->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + mViewers.erase(it++); + else + ++it; + } + for (std::deque::iterator it = mUsedViews.begin(); it != mUsedViews.end(); ) { - ViewData* vd = it->second; - if (vd->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) + if ((*it)->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) { - vd->clear(); - mUnusedViews.push_back(vd); - mViews.erase(it++); + (*it)->clear(); + mUnusedViews.push_back(*it); + it = mUsedViews.erase(it); } else ++it; } } -void ViewDataMap::clear() +void ViewDataMap::rebuildViews() { - mViews.clear(); - mUnusedViews.clear(); - mViewVector.clear(); + ++mWorldUpdateRevision; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 7f9f14af5..400d9fbbe 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -23,9 +23,11 @@ namespace Terrain void reset(); + bool suitableToUse(const osg::Vec4i& activeGrid) const; + void clear(); - bool contains(QuadTreeNode* node); + bool contains(QuadTreeNode* node) const; void copyFrom(const ViewData& other); @@ -57,6 +59,12 @@ namespace Terrain void setViewPoint(const osg::Vec3f& viewPoint); const osg::Vec3f& getViewPoint() const; + void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} } + const osg::Vec4i &getActiveGrid() const { return mActiveGrid;} + + unsigned int getWorldUpdateRevision() const { return mWorldUpdateRevision; } + void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; } + private: std::vector mEntries; unsigned int mNumEntries; @@ -64,34 +72,41 @@ namespace Terrain bool mChanged; osg::Vec3f mViewPoint; bool mHasViewPoint; + osg::Vec4i mActiveGrid; + unsigned int mWorldUpdateRevision; }; class ViewDataMap : public osg::Referenced { public: ViewDataMap() - : mReuseDistance(300) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. + : mReuseDistance(150) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. // this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time. , mExpiryDelay(1.f) + , mWorldUpdateRevision(0) {} - ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, bool& needsUpdate); + ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate); ViewData* createOrReuseView(); + ViewData* createIndependentView() const; void clearUnusedViews(double referenceTime); - - void clear(); + void rebuildViews(); + bool storeView(const ViewData* view, double referenceTime); private: std::list mViewVector; - typedef std::map, ViewData*> Map; - Map mViews; + typedef std::map, ViewData*> ViewerMap; + ViewerMap mViewers; float mReuseDistance; float mExpiryDelay; // time in seconds for unused view to be removed + unsigned int mWorldUpdateRevision; + + std::deque mUsedViews; std::deque mUnusedViews; }; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 2d53f4090..5b4807b38 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,7 +1,6 @@ #include "world.hpp" #include -#include #include #include @@ -23,11 +22,6 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); - mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); - osg::ref_ptr material (new osg::Material); - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); - mTerrainRoot->setName("Terrain Root"); osg::ref_ptr compositeCam = new osg::Camera; @@ -48,6 +42,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); + mChunkManager->setNodeMask(nodeMask); mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); mResourceSystem->addResourceManager(mChunkManager.get()); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index a69d03ca9..9f08454c8 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -139,6 +139,7 @@ namespace Terrain virtual void enable(bool enabled) {} virtual void setBordersVisible(bool visible); + virtual bool getBordersVisible() { return mBorderVisible; } /// Create a View to use with preload feature. The caller is responsible for deleting the view. /// @note Thread safe. @@ -146,11 +147,13 @@ namespace Terrain /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. - virtual void preload(View* view, const osg::Vec3f& viewPoint, std::atomic& abort) {} + virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. - virtual void storeView(const View* view, double referenceTime) {} + virtual bool storeView(const View* view, double referenceTime) {return true;} + + virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} @@ -160,6 +163,8 @@ namespace Terrain osg::Callback* getHeightCullCallback(float highz, unsigned int mask); + void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; } + protected: Storage* mStorage; @@ -180,6 +185,8 @@ namespace Terrain std::set> mLoadedCells; osg::ref_ptr mHeightCullCallback; + + osg::Vec4i mActiveGrid; }; } diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp index 84406c70c..8b0011dda 100644 --- a/components/widgets/fontwrapper.hpp +++ b/components/widgets/fontwrapper.hpp @@ -38,7 +38,7 @@ namespace Gui std::string getFontSize() { - // Note: we can not use the WindowManager here, so there is a code duplication a bit. + // Note: we can not use the FontLoader here, so there is a code duplication a bit. static const std::string fontSize = std::to_string(clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)); return fontSize; } diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index c8f4e16f8..bbe6b8336 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -140,3 +140,38 @@ The alpha value is currently ignored. This setting can only be configured by editing the settings configuration file. This setting has no effect if the crosshair setting in the HUD Settings Section is false. This setting has no effect if the show owned setting in the Game Settings Section is false. + +color topic enable +------------------ + +:Type: boolean +:Range: True/False +:Default: False + +Control wether additionnal formatting will be applied to dialogs topic. See 'color topic specific' and 'color topic exhausted' for details. + +color topic specific +-------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting overrides the color of keywords in the dialogue topic window. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +The color is overriden if the actor is about to give an answer that is unique to him (that is, dialogue with their object ID in the Actor field) that wasn't seen yet. + +color topic exhausted +--------------------- + +:Type: RGBA floating point +:Range: 0.0 to 1.0 +:Default: empty + +This setting overrides the color of keywords in the dialogue topic window. +The value is composed of four floating point values representing the red, green, blue and alpha channels. +The alpha value is currently ignored. + +The color is overridden if the next actor responses to the topic keyword has already been seen by the player. diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 92267a4a0..ecc1bda50 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -51,21 +51,20 @@ viewing distance This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, but also increase the amount of rendered geometry and significantly reduce the frame rate. -This value interacts with the exterior cell load distance setting -in that it's probably undesired for this value to provide visibility into cells that have not yet been loaded. -When cells are visible before loading, the geometry will "pop-in" suddenly, creating a jarring visual effect. -To prevent this effect, this value must be less than:: +Note that when cells are visible before loading (when not using a Distant Land), the geometry will "pop-in" suddenly, +creating a jarring visual effect. To prevent this effect, this value must be less than:: - (8192 * exterior cell load distance - 1024) * 0.93 + (CellSizeInUnits * CellGridRadius - 1024) * 0.93 -The constant 8192 is the size of a cell, and 1024 is the threshold distance for loading a new cell. +The CellSizeInUnits is the size of a game cell in units (8192 by default), CellGridRadius determines how many +neighboring cells to current one to load (1 by default - 3x3 grid), and 1024 is the threshold distance for loading a new cell. Additionally, the field of view setting also interacts with this setting because the view frustum end is a plane, so you can see further at the edges of the screen than you should be able to. This can be observed in game by looking at distant objects and rotating the camera so the objects are near the edge of the screen. As a result, this setting should further be reduced by a factor that depends on the field of view setting. In the default configuration this reduction is 7%, hence the factor of 0.93 above. -Using this factor, approximate values recommended for other exterior cell load distance settings are: +Using this factor, approximate values recommended for other CellGridRadius values are: ======= ======== Cells Viewing @@ -83,10 +82,6 @@ Such situations are unusual and probably not worth the performance penalty intro by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code. -Enabling the distant terrain setting is an alternative to increasing exterior cell load distance. -Note that the distant land setting does not include rendering of distant static objects, -so the resulting visual effect is not the same. - This setting can be adjusted in game from the ridiculously low value of 2048.0 to a maximum of 81920.0 using the View Distance slider in the Detail tab of the Video panel of the Options menu. @@ -118,3 +113,29 @@ because the Bethesda provided Morrowind assets do not adapt well to large values while small values can result in the hands not being visible. This setting can only be configured by editing the settings configuration file. + +view over shoulder +------------------ + +:Type: boolean +:Range: True/False +:Default: False + +This setting controls third person view mode. +False: View is centered on the character's head. Crosshair is hidden. +True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. + +This setting can only be configured by editing the settings configuration file. + +view over shoulder offset +------------------------- + +:Type: 2D vector floating point +:Range: Any +:Default: 30 -10 + +This setting makes sense only if 'view over shoulder' is enabled. Controls horizontal (first number) and vertical (second number) offset of the camera in third person mode. +Recommened values: 30 -10 for the right shoulder, -30 -10 for the left shoulder. + +This setting can only be configured by editing the settings configuration file. + diff --git a/docs/source/reference/modding/settings/cells.rst b/docs/source/reference/modding/settings/cells.rst index 43e51eb7a..620b545ac 100644 --- a/docs/source/reference/modding/settings/cells.rst +++ b/docs/source/reference/modding/settings/cells.rst @@ -1,30 +1,6 @@ Cells Settings ############## -exterior cell load distance ---------------------------- - -:Type: integer -:Range: >= 1 -:Default: 1 - -This setting determines the number of exterior cells adjacent to the character that will be loaded for rendering. - -.. Warning:: - Values greater than 1 will significantly affect the frame rate and loading times. - This setting is mainly intended for making screenshots of scenic vistas and not for real-time gameplay. - Loading more cells can break certain scripts or quests in the game that expect cells to not be loaded until the player is there. - These limitations will be addressed in a future version with a separate technique for rendering distant cells. - -This setting interacts with viewing distance and field of view settings. - -It is generally very wasteful for this value to load geometry than will almost never be visible -due to viewing distance and fog. For low frame rate screen shots of scenic vistas, -this setting should be set high, and viewing distances adjusted accordingly. - -This setting can only be configured by editing the settings configuration file. - - preload enabled --------------- @@ -60,7 +36,7 @@ consider increasing the number of preloading threads to 2 or 3 for a boost in pr Faster preloading will reduce the chance that a cell could not be completely loaded before the player moves into it, and hence reduce the chance of seeing loading screens or frame drops. This may be especially relevant when the player moves at high speed -and/or a large number of cells are loaded in via 'exterior cell load distance'. +and/or a large number of objects are preloaded due to large viewing distance. A value of 4 or higher is not recommended. With 4 or more threads, improvements will start to diminish due to file reading and synchronization bottlenecks. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 201704f35..c27796aec 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -315,3 +315,18 @@ Setting the value of this setting to true will remove the 0 lower cap from the v allowing Damage Fatigue to reduce Fatigue to a value below zero. This setting can be controlled in Advanced tab of the launcher. + +turn to movement direction +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Affects side and diagonal movement. Enabling this setting makes movement more realistic. + +If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding. + +If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index 3ef376bb9..ac989f3de 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -103,14 +103,3 @@ and typically require more panning to see all available portions of the map. This larger size also enables an overall greater level of detail if the local map resolution setting is also increased. This setting can not be configured except by editing the settings configuration file. - -local map cell distance ------------------------ - -:Type: integer -:Range: >= 1 -:Default: 1 - -Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. -Please note that only loaded cells can be rendered, -so this setting must be lower or equal to "exterior cell load distance" to work properly. diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst index d0d92a6e2..0670a8109 100644 --- a/docs/source/reference/modding/settings/shadows.rst +++ b/docs/source/reference/modding/settings/shadows.rst @@ -71,7 +71,7 @@ Enable or disable the debug hud to see what the shadow map(s) contain. This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. enable debug overlay ----------------- +-------------------- :Type: boolean :Range: True/False @@ -80,15 +80,15 @@ enable debug overlay Enable or disable the debug overlay to see the area covered by each shadow map. This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. -compute tight scene bounds --------------------------- +compute scene bounds +-------------------- -:Type: boolean -:Range: True/False -:Default: True +:Type: string +:Range: primitives|bounds|none +:Default: bounding volumes -With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. -May have a minor to major performance impact. +Two different ways to make better use of shadow map(s) by making them cover a smaller area. +While primitives give better shadows at expense of more CPU, bounds gives better performance overall but with lower quality shadows. There is also the ability to disable this computation with none. shadow map resolution --------------------- diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt index 78a4e6034..6cf2dc935 100644 --- a/extern/osgQt/CMakeLists.txt +++ b/extern/osgQt/CMakeLists.txt @@ -8,12 +8,7 @@ set(OSGQT_SOURCE_FILES 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() - target_link_libraries(${OSGQT_LIBRARY} Qt5::Core Qt5::OpenGL) -endif() +target_link_libraries(${OSGQT_LIBRARY} Qt5::Core Qt5::OpenGL) link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt index 274f0a149..5b1e53734 100644 --- a/extern/osgQt/GraphicsWindowQt +++ b/extern/osgQt/GraphicsWindowQt @@ -41,9 +41,9 @@ class GLWidget : public QGLWidget public: - GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); - GLWidget( QGLContext* context, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); - GLWidget( const QGLFormat& format, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0); + GLWidget( QWidget* parent = nullptr, const QGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + GLWidget( QGLContext* context, QWidget* parent = nullptr, const QGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + GLWidget( const QGLFormat& format, QWidget* parent = nullptr, const QGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~GLWidget(); inline void setGraphicsWindow( GraphicsWindowQt* gw ) { _gw = gw; } @@ -93,7 +93,7 @@ protected: class GraphicsWindowQt : public osgViewer::GraphicsWindow { public: - GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0 ); + GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = nullptr, const QGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); GraphicsWindowQt( GLWidget* widget ); virtual ~GraphicsWindowQt(); @@ -107,7 +107,7 @@ public: struct WindowData : public osg::Referenced { - WindowData( GLWidget* widget = NULL, QWidget* parent = NULL ): _widget(widget), _parent(parent) {} + WindowData( GLWidget* widget = nullptr, QWidget* parent = nullptr ): _widget(widget), _parent(parent) {} GLWidget* _widget; QWidget* _parent; }; diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp index aa9b4bbdb..784cf9179 100644 --- a/extern/osgQt/GraphicsWindowQt.cpp +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -17,33 +17,26 @@ #include #include #include - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include -#endif using namespace osgQt; -#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) - #define GETDEVICEPIXELRATIO() 1.0 -#else - #define GETDEVICEPIXELRATIO() devicePixelRatio() -#endif +#define GETDEVICEPIXELRATIO() devicePixelRatio() GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) -: QGLWidget(parent, shareWidget, f), _gw( NULL ) +: QGLWidget(parent, shareWidget, f), _gw( nullptr ) { _devicePixelRatio = GETDEVICEPIXELRATIO(); } GLWidget::GLWidget( QGLContext* context, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) -: QGLWidget(context, parent, shareWidget, f), _gw( NULL ) +: QGLWidget(context, parent, shareWidget, f), _gw( nullptr ) { _devicePixelRatio = GETDEVICEPIXELRATIO(); } GLWidget::GLWidget( const QGLFormat& format, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) -: QGLWidget(format, parent, shareWidget, f), _gw( NULL ) +: QGLWidget(format, parent, shareWidget, f), _gw( nullptr ) { _devicePixelRatio = GETDEVICEPIXELRATIO(); } @@ -54,8 +47,8 @@ GLWidget::~GLWidget() if( _gw ) { _gw->close(); - _gw->_widget = NULL; - _gw = NULL; + _gw->_widget = nullptr; + _gw = nullptr; } } @@ -119,13 +112,11 @@ bool GLWidget::event( QEvent* event ) enqueueDeferredEvent(QEvent::ParentChange); return true; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) else if (event->type() == QEvent::PlatformSurface && static_cast(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { if (_gw) _gw->close(); } -#endif // perform regular event handling return QGLWidget::event( event ); @@ -164,7 +155,7 @@ GraphicsWindowQt::GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidge : _realized(false) { - _widget = NULL; + _widget = nullptr; _traits = traits; init( parent, shareWidget, f ); } @@ -174,7 +165,7 @@ GraphicsWindowQt::GraphicsWindowQt( GLWidget* widget ) { _widget = widget; _traits = _widget ? createTraits( _widget ) : new osg::GraphicsContext::Traits; - init( NULL, NULL, 0 ); + init( nullptr, nullptr, Qt::WindowFlags() ); } GraphicsWindowQt::~GraphicsWindowQt() @@ -183,20 +174,20 @@ GraphicsWindowQt::~GraphicsWindowQt() // remove reference from GLWidget if ( _widget ) - _widget->_gw = NULL; + _widget->_gw = nullptr; } 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; + WindowData* windowData = _traits.get() ? dynamic_cast(_traits->inheritedWindowData.get()) : nullptr; if ( !_widget ) - _widget = windowData ? windowData->_widget : NULL; + _widget = windowData ? windowData->_widget : nullptr; if ( !parent ) - parent = windowData ? windowData->_parent : NULL; + parent = windowData ? windowData->_parent : nullptr; // create widget if it does not exist - _ownsWidget = _widget == NULL; + _ownsWidget = _widget == nullptr; if ( !_widget ) { // shareWidget @@ -209,11 +200,7 @@ bool GraphicsWindowQt::init( QWidget* parent, const QGLWidget* shareWidget, Qt:: // 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 - ; + flags |= Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; // create widget _widget = new GLWidget( traits2qglFormat( _traits.get() ), parent, shareWidget, flags ); @@ -323,7 +310,7 @@ osg::GraphicsContext::Traits* GraphicsWindowQt::createTraits( const QGLWidget* w bool GraphicsWindowQt::setWindowRectangleImplementation( int x, int y, int width, int height ) { - if ( _widget == NULL ) + if ( _widget == nullptr ) return false; _widget->setGeometry( x, y, width, height ); @@ -527,11 +514,10 @@ bool GraphicsWindowQt::releaseContextImplementation() void GraphicsWindowQt::swapBuffersImplementation() { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) // QOpenGLContext complains if we swap on an non-exposed QWindow if (!_widget || !_widget->windowHandle()->isExposed()) return; -#endif + // 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 @@ -610,13 +596,13 @@ public: if (traits->pbuffer) { OSG_WARN << "osgQt: createGraphicsContext - pbuffer not implemented yet." << std::endl; - return NULL; + return nullptr; } else { osg::ref_ptr< GraphicsWindowQt > window = new GraphicsWindowQt( traits ); if (window->valid()) return window.release(); - else return NULL; + else return nullptr; } } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5b587776c..bc07463a1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -33,11 +33,13 @@ field of view = 60.0 # Best to leave this at the default since vanilla assets are not complete enough to adapt to high FoV's. Too low FoV would clip the hands off screen. first person field of view = 60.0 -[Cells] +# If enabled then third person camera is positioned above character's shoulder and crosshair is visible. +view over shoulder = false + +# Makes sense only if 'view over shoulder' is true. First number is horizontal offset (negative value means offset to the left), second number is vertical offset. +view over shoulder offset = 30 -10 -# Adjacent exterior cells loaded (>0). Caution: this setting can -# dramatically affect performance, see documentation for details. -exterior cell load distance = 1 +[Cells] # Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled. preload enabled = true @@ -106,6 +108,27 @@ composite map resolution = 512 # Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. max composite geometry size = 4.0 +# Use object paging for non active cells +object paging = true + +# Use object paging for active cells grid +object paging active grid = false + +# Affects the likelyhood of objects being merged. A higher value means merging is more likely and may improve FPS at the cost of memory. +object paging merge factor = 250 + +# Cull objects smaller than this size divided by distance +object paging min size = 0.01 + +# Adjusts 'min size' based on merging decision. Allows inexpensive objects to be rendered from a greater distance. +object paging min size merge factor = 0.3 + +# Controls how inexpensive an object needs to be to utilize 'min size merge factor'. +object paging min size cost multiplier = 25 + +# Assign a random color to merged batches. +object paging debug batches = false + [Fog] # If true, use extended fog parameters for distant terrain not controlled by @@ -145,11 +168,6 @@ 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 - # If true, map in world mode, otherwise in local mode global = false @@ -190,6 +208,19 @@ color crosshair owned = 1.0 0.15 0.15 1.0 # Controls whether Arrow keys, Movement keys, Tab/Shift-Tab and Spacebar/Enter/Activate may be used to navigate GUI buttons. keyboard navigation = true +# Apply the defined color to dialogue topic. +color topic enable = false + +# The color of dialogue topic keywords that gives unique actor responses +# Format R G B A or empty for no special formatting +# Default to blue +color topic specific = 0.45 0.5 0.8 1 + +# The color of dialogue topic keywords that gives already read responses +# Format R G B A or empty for no special formatting +# Default to grey +color topic exhausted = 0.3 0.3 0.3 1 + [HUD] # Displays the crosshair or reticle when not in GUI mode. @@ -276,6 +307,9 @@ projectiles enchant multiplier = 0 # This means that unlike Morrowind you will be able to knock down actors using this effect. uncapped damage fatigue = false +# Turn lower body to movement direction. 'true' makes diagonal movement more realistic. +turn to movement direction = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). @@ -808,8 +842,8 @@ enable debug hud = false # Enable the debug overlay to see where each shadow map affects. enable debug overlay = false -# Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact. -compute tight scene bounds = true +# Used to set the type of tight scene bound calculation method to be used by the shadow map that covers a smaller area. "bounds" (default) is less precise shadows but better performance or "primitives" for more precise shadows at expense of CPU. +compute scene bounds = bounds # How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. shadow map resolution = 1024 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index e5587a448..1ed162eea 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -29,25 +29,27 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) #endif { - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = gl_FrontMaterial.ambient.xyz; - vec3 emission = gl_FrontMaterial.emission.xyz; + vec4 diffuse; + vec3 ambient; if (colorMode == ColorMode_AmbientAndDiffuse) { diffuse = vertexColor; ambient = vertexColor.xyz; } - else if (colorMode == ColorMode_Ambient) - { - ambient = vertexColor.xyz; - } else if (colorMode == ColorMode_Diffuse) { diffuse = vertexColor; + ambient = gl_FrontMaterial.ambient.xyz; } - else if (colorMode == ColorMode_Emission) + else if (colorMode == ColorMode_Ambient) { - emission = vertexColor.xyz; + diffuse = gl_FrontMaterial.diffuse; + ambient = vertexColor.xyz; + } + else + { + diffuse = gl_FrontMaterial.diffuse; + ambient = gl_FrontMaterial.ambient.xyz; } vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); @@ -65,7 +67,12 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadow lightResult.xyz += ambientLight + diffuseLight; } - lightResult.xyz += gl_LightModel.ambient.xyz * ambient + emission; + lightResult.xyz += gl_LightModel.ambient.xyz * ambient; + + if (colorMode == ColorMode_Emission) + lightResult.xyz += vertexColor.xyz; + else + lightResult.xyz += gl_FrontMaterial.emission.xyz; #if @clamp lightResult = clamp(lightResult, vec4(0.0), vec4(1.0)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 78cc8c1dd..d5716c378 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -49,7 +49,7 @@ uniform vec2 envMapLumaBias; uniform mat2 bumpMapMatrix; #endif -uniform bool simpleWater = false; +uniform bool simpleWater; varying float euclideanDepth; varying float linearDepth; @@ -83,7 +83,9 @@ void main() mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#else +#endif + +#if (!@normalMap && (@parallax || @forcePPL)) vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); #endif @@ -176,17 +178,27 @@ void main() vec3 matSpec = specTex.xyz; #else float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = gl_FrontMaterial.specular.xyz; + vec3 matSpec; if (colorMode == ColorMode_Specular) matSpec = passColor.xyz; + else + matSpec = gl_FrontMaterial.specular.xyz; #endif - gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + if (matSpec != vec3(0.0)) + { +#if (!@normalMap && !@parallax && !@forcePPL) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + } #if @radialFog - float depth = euclideanDepth; + float depth; // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis if (simpleWater) depth = length(passViewPos); + else + depth = euclideanDepth; float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #else float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index bd97b2526..40c448de9 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -63,7 +63,9 @@ void main(void) euclideanDepth = length(viewPos.xyz); linearDepth = gl_Position.z; +#if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); +#endif #if @envMap vec3 viewVec = normalize(viewPos.xyz); @@ -112,5 +114,7 @@ void main(void) passViewPos = viewPos.xyz; passNormal = gl_Normal.xyz; +#if (@shadows_enabled) setupShadowCoords(viewPos, viewNormal); +#endif } diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 477b1bf9e..7409ce045 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -43,8 +43,10 @@ void main() mat3 tbnTranspose = mat3(tangent, binormal, normalizedNormal); vec3 viewNormal = normalize(gl_NormalMatrix * (tbnTranspose * (normalTex.xyz * 2.0 - 1.0))); -#else - vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); +#endif + +#if (!@normalMap && (@parallax || @forcePPL)) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); #endif #if @parallax @@ -85,12 +87,20 @@ void main() vec3 matSpec = vec3(diffuseTex.a); #else float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = gl_FrontMaterial.specular.xyz; + vec3 matSpec; if (colorMode == ColorMode_Specular) matSpec = passColor.xyz; + else + matSpec = gl_FrontMaterial.specular.xyz; #endif - gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec) * shadowing; + if (matSpec != vec3(0.0)) + { +#if (!@normalMap && !@parallax && !@forcePPL) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec) * shadowing; + } #if @radialFog float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index dc4ea802f..bf337cf54 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -26,8 +26,10 @@ void main(void) gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = gl_Position.z; - + +#if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); +#endif #if !PER_PIXEL_LIGHTING lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting); @@ -38,5 +40,7 @@ void main(void) uv = gl_MultiTexCoord0.xy; +#if (@shadows_enabled) setupShadowCoords(viewPos, viewNormal); +#endif } diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 86edd8698..93ba156be 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -36,7 +36,7 @@ const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness -const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be supressed for reflections and refractions (prevents artifacts at shores) +const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 2ea5d777c..fed547b8c 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -459,6 +459,16 @@ + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be change in settings.cfg.</p><p>The default value is false.</p></body></html> + + + Change dialogue topic color + + + diff --git a/files/ui/contentselector.ui b/files/ui/contentselector.ui index d13cb314e..c099649b3 100644 --- a/files/ui/contentselector.ui +++ b/files/ui/contentselector.ui @@ -66,6 +66,9 @@ + + + diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index 18d220797..cfa2c800c 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -201,49 +201,97 @@ - - + + + - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Shadow Distance Limit: + Enable Player Shadows - - - - false + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + Enable Actor Shadows + + + + + - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - - unit(s) + + Enable Object Shadows - - 512 + + + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - - 81920 + + Enable Terrain Shadows - - 8192 + + + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + Enable Indoor Shadows - - + + - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - Fade Start Multiplier: + Shadow Near Far Computation Method: - + compute scene bounds + + + + bounds + + + + + primitives + + + + + none + + + + + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + Shadow Map Resolution: + + + + @@ -267,85 +315,68 @@ - - - - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - - - Enable Actor Shadows - - - - - + + - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Enable Terrain Shadows + Shadow Distance Limit: - + false - - 2 + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + unit(s) - 0 + 512 - 1 + 81920 - 0.90 + 8192 - - + + - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - Enable Player Shadows + Fade Start Multiplier: - - - - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - - - Shadow Map Resolution: + + + + false - - - - - - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + 2 - - Enable Object Shadows + + 0 - - - - - - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + 1 - - Enable Indoor Shadows + + 0.90 +