1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-30 16:45:33 +00:00

Add OpenMW commits up to 15 Oct 2020

# Conflicts:
#   .travis.yml
#   CI/before_script.linux.sh
#   CMakeLists.txt
#   apps/openmw/mwgui/containeritemmodel.cpp
#   apps/openmw/mwgui/tradewindow.cpp
#   apps/openmw/mwphysics/actor.cpp
#   apps/openmw/mwworld/actionteleport.cpp
#   apps/openmw/mwworld/containerstore.cpp
This commit is contained in:
David Cernat 2020-10-15 19:51:39 +02:00
commit 68837aaf4a
115 changed files with 3064 additions and 1278 deletions

View file

@ -1,50 +1,86 @@
stages: stages:
- build - build
Debian: .Debian:
tags: tags:
- docker - docker
- linux - linux
image: debian:bullseye image: debian:bullseye
cache: cache:
key: cache.002
paths: paths:
- apt-cache/ - apt-cache/
- ccache/ - ccache/
before_script: before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq - apt-get update -yq
- 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 ccache - 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 ccache git clang
stage: build stage: build
script: script:
- export CCACHE_BASEDIR="`pwd`" - export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
- ccache -z -M 250M - ccache -z -M "${CCACHE_SIZE}"
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi - CI/before_script.linux.sh
- mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - cd build
- make -j$cores_to_use - cmake --build . -- -j $(nproc)
- DESTDIR=artifacts make install - cmake --install .
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi
- ccache -s - ccache -s
artifacts: artifacts:
paths: paths:
- build/artifacts/ - build/install/
Debian_GCC:
extends: .Debian
cache:
key: Debian_GCC.v2
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 3G
Debian_GCC_tests:
extends: .Debian
cache:
key: Debian_GCC_tests.v2
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
Debian_Clang:
extends: .Debian
cache:
key: Debian_Clang.v2
variables:
CC: clang
CXX: clang++
CCACHE_SIZE: 2G
Debian_Clang_tests:
extends: .Debian
cache:
key: Debian_Clang_tests.v2
variables:
CC: clang
CXX: clang++
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
MacOS: MacOS:
tags: tags:
- macos - macos
- xcode
except:
- branches # because our CI VMs are not public, MRs can't use them and timeout
stage: build stage: build
allow_failure: true
script: script:
- rm -fr build/* # remove anything in the build directory - rm -fr build/* # remove anything in the build directory
- CI/before_install.osx.sh - CI/before_install.osx.sh
- CI/before_script.osx.sh - CI/before_script.osx.sh
- cd build; make -j2 package - cd build; make -j2 package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
artifacts: artifacts:
paths: paths:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log"
variables: &engine-targets variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
@ -228,4 +264,4 @@ Windows_MSBuild_CS_RelWithDebInfo:
- .Windows_MSBuild_Base - .Windows_MSBuild_Base
variables: variables:
<<: *cs-targets <<: *cs-targets
config: "RelWithDebInfo" config: "RelWithDebInfo"

View file

@ -74,7 +74,7 @@ script:
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}" - cd "${TRAVIS_BUILD_DIR}"
- ccache -s - ccache -s

View file

@ -5,14 +5,18 @@
Bug #1952: Incorrect particle lighting Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #2473: Unable to overstock merchants
Bug #3676: NiParticleColorModifier isn't applied properly Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3862: Random container contents behave differently than vanilla
Bug #3929: Leveled list merchant containers respawn on barter
Bug #4021: Attributes and skills are not stored as floats Bug #4021: Attributes and skills are not stored as floats
Bug #4055: Local scripts don't inherit variables from their base record Bug #4055: Local scripts don't inherit variables from their base record
Bug #4623: Corprus implementation is incorrect Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem Bug #4764: Data race in osg ParticleSystem
Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5101: Hostile followers travel with the player
Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps Bug #5165: Active spells should use real time intead of timestamps
Bug #5358: ForceGreeting always resets the dialogue window completely Bug #5358: ForceGreeting always resets the dialogue window completely
@ -47,8 +51,10 @@
Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
Bug #5557: Diagonal movement is noticeably slower with analogue stick Bug #5557: Diagonal movement is noticeably slower with analogue stick
Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics
Bug #5603: Setting constant effect cast style doesn't correct effects view Bug #5603: Setting constant effect cast style doesn't correct effects view
Bug #5611: Usable items with "0 Uses" should be used only once Bug #5611: Usable items with "0 Uses" should be used only once
Bug #5622: Can't properly interact with the console when in pause menu
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging Feature #2386: Distant Statics in the form of Object Paging
Feature #4894: Consider actors as obstacles for pathfinding Feature #4894: Consider actors as obstacles for pathfinding
@ -61,8 +67,10 @@
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8) Feature #5525: Search fields tweaks (utf-8)
Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5545: Option to allow stealing from an unconscious NPC during combat
Feature #5563: Run physics update in background thread
Feature #5579: MCP SetAngle enhancement Feature #5579: MCP SetAngle enhancement
Feature #5610: Actors movement should be smoother Feature #5610: Actors movement should be smoother
Feature #5642: Ability to attach arrows to actor skeleton instead of bow mesh
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation

View file

@ -1,6 +1,9 @@
#!/bin/sh -e #!/bin/sh -e
brew install ccache # Some of these tools can come from places other than brew, so check before installing
command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-ef2462c.zip -o ~/openmw-deps.zip curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-ef2462c.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

62
CI/before_script.linux.sh Executable file → Normal file
View file

@ -2,8 +2,10 @@
free -m free -m
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh if [[ "${BUILD_TESTS_ONLY}" ]]; then
GOOGLETEST_DIR="$(pwd)/googletest/build" export GOOGLETEST_DIR="$(pwd)/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
fi
mkdir build mkdir build
cd build cd build
@ -15,22 +17,40 @@ fi
export RAKNET_ROOT=~/CrabNet export RAKNET_ROOT=~/CrabNet
${ANALYZE} cmake .. \ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DCMAKE_C_COMPILER="${CC}" \ ${ANALYZE} cmake \
-DCMAKE_CXX_COMPILER="${CXX}" \ -D CMAKE_C_COMPILER="${CC}" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -D CMAKE_CXX_COMPILER="${CXX}" \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -D CMAKE_C_COMPILER_LAUNCHER=ccache \
-DBUILD_OPENCS=OFF \ -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DUSE_SYSTEM_TINYXML=1 \ -D CMAKE_INSTALL_PREFIX=install \
-DCMAKE_INSTALL_PREFIX=/usr \ -D CMAKE_BUILD_TYPE=RelWithDebInfo \
-DBINDIR=/usr/games \ -D USE_SYSTEM_TINYXML=TRUE \
-DCMAKE_BUILD_TYPE="None" \ -D BUILD_OPENMW=OFF \
-DBUILD_UNITTESTS=TRUE \ -D BUILD_OPENMW_MP=OFF \
-DUSE_SYSTEM_TINYXML=TRUE \ -D BUILD_BSATOOL=OFF \
-DCMAKE_INSTALL_PREFIX="/usr" \ -D BUILD_ESMTOOL=OFF \
-DBINDIR="/usr/games" \ -D BUILD_LAUNCHER=OFF \
-DCMAKE_BUILD_TYPE="DEBUG" \ -D BUILD_BROWSER=OFF \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \ -D BUILD_MWINIIMPORTER=OFF \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \ -D BUILD_ESSIMPORTER=OFF \
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \ -D BUILD_OPENCS=OFF \
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a -D BUILD_WIZARD=OFF \
-D BUILD_UNITTESTS=ON \
-D GTEST_ROOT="${GOOGLETEST_DIR}" \
-D GMOCK_ROOT="${GOOGLETEST_DIR}" \
..
else
${ANALYZE} cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D BUILD_OPENCS=OFF \
-D USE_SYSTEM_TINYXML=TRUE \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=Debug \
-D RakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
-D RakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
..
fi

View file

@ -523,7 +523,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost # Boost
if [ -z $APPVEYOR ]; then if [ -z $APPVEYOR ]; then
download "Boost ${BOOST_VER}" \ download "Boost ${BOOST_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/${BOOST_VER}/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \
"boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe" "boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe"
fi fi

View file

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" \ -D CMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" \
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \
-D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \ -D BUILD_OPENMW=TRUE \

View file

@ -1,13 +1,17 @@
#!/bin/sh -e #!/bin/sh -ex
git clone -b release-1.10.0 https://github.com/google/googletest.git git clone -b release-1.10.0 https://github.com/google/googletest.git
cd googletest cd googletest
mkdir build mkdir build
cd build cd build
cmake \ cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \
-D CMAKE_INSTALL_PREFIX=. \ -D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
-G "${GENERATOR}" \ -G "${GENERATOR}" \
.. ..
cmake --build . --config "${CONFIGURATION}" cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
cmake --build . --target install --config "${CONFIGURATION}" cmake --install . --config "${CONFIGURATION}"

View file

@ -458,172 +458,6 @@ elseif (MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE")
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
IF(NOT WIN32 AND NOT APPLE)
# Linux installation
# Install binaries
IF(BUILD_OPENMW)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENMW)
IF(BUILD_OPENMW_MP)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-server" DESTINATION "${BINDIR}")
ENDIF(BUILD_OPENMW_MP)
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" )
ENDIF(BUILD_LAUNCHER)
IF(BUILD_BROWSER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-browser" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BROWSER)
IF(BUILD_BSATOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BSATOOL)
IF(BUILD_ESMTOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESMTOOL)
IF(BUILD_NIFTEST)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" )
ENDIF(BUILD_NIFTEST)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-cs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
IF(BUILD_BROWSER)
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-browser.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "browser")
ENDIF(BUILD_BROWSER)
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install global configuration files
INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
#INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp")
#INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp")
#They both do not exist
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
ENDIF(NOT WIN32 AND NOT APPLE)
if(WIN32)
FILE(GLOB dll_files_debug "${OpenMW_BINARY_DIR}/Debug/*.dll")
FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
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)
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY ${plugin_dir_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
SET(CPACK_GENERATOR "NSIS")
SET(CPACK_PACKAGE_NAME "OpenMW")
SET(CPACK_PACKAGE_VENDOR "OpenMW.org")
SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW")
IF(BUILD_LAUNCHER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher")
ENDIF(BUILD_LAUNCHER)
IF(BUILD_BROWSER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};tes3mp-browser;tes3mp Launcher")
ENDIF(BUILD_BROWSER)
IF(BUILD_OPENCS)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set")
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard")
ENDIF(BUILD_WIZARD)
SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'")
SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\"
")
SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
if(EXISTS ${OALREDIST})
INSTALL(FILES ${OALREDIST} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" )
endif(EXISTS ${OALREDIST})
if(CMAKE_CL_64)
SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
endif()
include(CPack)
endif(WIN32)
# Extern # Extern
IF(BUILD_OPENMW OR BUILD_OPENCS) IF(BUILD_OPENMW OR BUILD_OPENCS)
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
@ -938,7 +772,193 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\")
" COMPONENT Runtime) " COMPONENT Runtime)
include(CPack) include(CPack)
endif () elseif(NOT APPLE)
get_generator_is_multi_config(multi_config)
if (multi_config)
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$<CONFIG>")
else ()
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}")
endif ()
if(WIN32)
INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll"
PATTERN "deps" EXCLUDE
PATTERN "apps" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE
PATTERN "components" EXCLUDE
PATTERN "docs" EXCLUDE
PATTERN "extern" EXCLUDE
PATTERN "files" EXCLUDE
PATTERN "Testing" EXCLUDE)
INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb"
PATTERN "deps" EXCLUDE
PATTERN "apps" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE
PATTERN "components" EXCLUDE
PATTERN "docs" EXCLUDE
PATTERN "extern" EXCLUDE
PATTERN "files" EXCLUDE
PATTERN "Testing" EXCLUDE)
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".")
# Start of tes3mp addition
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-client-default.cfg" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-server-default.cfg" DESTINATION ".")
# End of tes3mp addition
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".")
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".")
SET(CPACK_GENERATOR "NSIS")
SET(CPACK_PACKAGE_NAME "OpenMW")
SET(CPACK_PACKAGE_VENDOR "OpenMW.org")
SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW")
IF(BUILD_LAUNCHER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher")
ENDIF(BUILD_LAUNCHER)
# Start of tes3mp addition
IF(BUILD_BROWSER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};tes3mp-browser;tes3mp Launcher")
ENDIF(BUILD_BROWSER)
# End of tes3mp addition
IF(BUILD_OPENCS)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set")
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard")
ENDIF(BUILD_WIZARD)
SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'")
SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\"
")
SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
# Start of tes3mp change (major)
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
# End of tes3mp change (major)
SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
if(EXISTS ${OALREDIST})
INSTALL(FILES ${OALREDIST} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" )
endif(EXISTS ${OALREDIST})
if(CMAKE_CL_64)
SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
endif()
include(CPack)
else(WIN32)
# Linux installation
# Install binaries
IF(BUILD_OPENMW)
# Start of tes3mp change (major)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/tes3mp" DESTINATION "${BINDIR}" )
# End of tes3mp change (major)
ENDIF(BUILD_OPENMW)
# Start of tes3mp addition
IF(BUILD_OPENMW_MP)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/tes3mp-server" DESTINATION "${BINDIR}")
ENDIF(BUILD_OPENMW_MP)
# End of tes3mp addition
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" )
ENDIF(BUILD_LAUNCHER)
# Start of tes3mp addition
IF(BUILD_BROWSER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/tes3mp-browser" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BROWSER)
# End of tes3mp addition
IF(BUILD_BSATOOL)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/bsatool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BSATOOL)
IF(BUILD_ESMTOOL)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/esmtool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESMTOOL)
IF(BUILD_NIFTEST)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/niftest" DESTINATION "${BINDIR}" )
ENDIF(BUILD_NIFTEST)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-iniimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-cs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
# Start of tes3mp addition
IF(BUILD_BROWSER)
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-browser.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "browser")
ENDIF(BUILD_BROWSER)
# End of tes3mp addition
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install global configuration files
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
# Start of tes3mp addition
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-client-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-server-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp")
# End of tes3mp addition
IF(BUILD_OPENCS)
INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
endif(WIN32)
endif(OPENMW_OSX_DEPLOYMENT AND APPLE)
# Doxygen Target -- simply run 'make doc' or 'make doc_pages' # Doxygen Target -- simply run 'make doc' or 'make doc_pages'
# output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen" # output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen"

View file

@ -94,6 +94,9 @@ bool Launcher::AdvancedPage::loadSettings()
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
if (numPhysicsThreads >= 0)
physicsThreadsSpinBox->setValue(numPhysicsThreads);
} }
// Visuals // Visuals
@ -208,6 +211,9 @@ void Launcher::AdvancedPage::saveSettings()
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = physicsThreadsSpinBox->value();
if (numPhysicsThreads != mEngineSettings.getInt("async num threads", "Physics"))
mEngineSettings.setInt("async num threads", "Physics", numPhysicsThreads);
} }
// Visuals // Visuals

View file

@ -233,8 +233,15 @@ target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)
if (WIN32) if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) get_generator_is_multi_config(multi_config)
if (multi_config)
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$<CONFIG>")
else ()
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}")
endif ()
INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION ".")
endif() endif()
if (MSVC) if (MSVC)

View file

@ -73,7 +73,7 @@ add_openmw_dir (mwworld
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
closestnotmeconvexresultcallback raycasting closestnotmeconvexresultcallback raycasting mtphysics
) )
add_openmw_dir (mwclass add_openmw_dir (mwclass

View file

@ -6,6 +6,7 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp" #include "../mwmechanics/movement.hpp"
#include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/magiceffects.hpp"
@ -79,7 +80,8 @@ namespace MWClass
float weight = getContainerStore(ptr).getWeight(); float weight = getContainerStore(ptr).getWeight();
const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects();
weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude();
weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); if (ptr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->getGodModeState())
weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude();
return (weight < 0) ? 0.0f : weight; return (weight < 0) ? 0.0f : weight;
} }

View file

@ -43,44 +43,41 @@
namespace MWClass namespace MWClass
{ {
class ContainerCustomData : public MWWorld::CustomData ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell)
{ {
public: unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max());
MWWorld::ContainerStore mContainerStore; // setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
mStore.fillNonRandom(container.mInventory, "", seed);
}
virtual MWWorld::CustomData *clone() const; ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory)
{
virtual ContainerCustomData& asContainerCustomData() mStore.readState(inventory);
{ }
return *this;
}
virtual const ContainerCustomData& asContainerCustomData() const
{
return *this;
}
};
MWWorld::CustomData *ContainerCustomData::clone() const MWWorld::CustomData *ContainerCustomData::clone() const
{ {
return new ContainerCustomData (*this); return new ContainerCustomData (*this);
} }
ContainerCustomData& ContainerCustomData::asContainerCustomData()
{
return *this;
}
const ContainerCustomData& ContainerCustomData::asContainerCustomData() const
{
return *this;
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<ContainerCustomData> data (new ContainerCustomData); MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
MWWorld::LiveCellRef<ESM::Container> *ref =
ptr.get<ESM::Container>();
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
data->mContainerStore.fill(
ref->mBase->mInventory, "");
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release());
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
} }
@ -110,17 +107,6 @@ namespace MWClass
} }
} }
void Container::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
store.restock(list, ptr, "");
}
void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{ {
if (!model.empty()) { if (!model.empty()) {
@ -292,12 +278,12 @@ namespace MWClass
return !name.empty() ? name : ref->mBase->mId; return !name.empty() ? name : ref->mBase->mId;
} }
MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const
const
{ {
ensureCustomData (ptr); ensureCustomData (ptr);
auto& data = ptr.getRefData().getCustomData()->asContainerCustomData();
return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore; data.mStore.mPtr = ptr;
return data.mStore;
} }
std::string Container::getScript (const MWWorld::ConstPtr& ptr) const std::string Container::getScript (const MWWorld::ConstPtr& ptr) const
@ -317,8 +303,7 @@ namespace MWClass
bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const
{ {
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems(); return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems();
return true; return true;
} }
@ -381,16 +366,8 @@ namespace MWClass
if (!state.mHasCustomState) if (!state.mHasCustomState)
return; return;
if (!ptr.getRefData().getCustomData())
{
// Create a CustomData, but don't fill it from ESM records (not needed)
std::unique_ptr<ContainerCustomData> data (new ContainerCustomData);
ptr.getRefData().setCustomData (data.release());
}
ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
const ESM::ContainerState& containerState = state.asContainerState(); const ESM::ContainerState& containerState = state.asContainerState();
customData.mContainerStore.readState (containerState.mInventory); ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release());
} }
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
@ -402,7 +379,13 @@ namespace MWClass
} }
const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
if (!customData.mStore.isResolved())
{
state.mHasCustomState = false;
return;
}
ESM::ContainerState& containerState = state.asContainerState(); ESM::ContainerState& containerState = state.asContainerState();
customData.mContainerStore.writeState (containerState.mInventory); customData.mStore.writeState (containerState.mInventory);
} }
} }

View file

@ -2,9 +2,32 @@
#define GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/customdata.hpp"
namespace ESM
{
struct Container;
struct InventoryState;
}
namespace MWClass namespace MWClass
{ {
class ContainerCustomData : public MWWorld::CustomData
{
MWWorld::ContainerStore mStore;
public:
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
ContainerCustomData(const ESM::InventoryState& inventory);
virtual MWWorld::CustomData *clone() const;
virtual ContainerCustomData& asContainerCustomData();
virtual const ContainerCustomData& asContainerCustomData() const;
friend class Container;
};
class Container : public MWWorld::Class class Container : public MWWorld::Class
{ {
void ensureCustomData (const MWWorld::Ptr& ptr) const; void ensureCustomData (const MWWorld::Ptr& ptr) const;
@ -70,8 +93,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const;
virtual void restock (const MWWorld::Ptr &ptr) const;
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;
virtual bool useAnim() const; virtual bool useAnim() const;

View file

@ -1030,14 +1030,6 @@ namespace MWClass
} }
} }
void Creature::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
store.restock(list, ptr, ptr.getCellRef().getRefId());
}
int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const
{ {
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();

View file

@ -133,8 +133,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const;
virtual void restock (const MWWorld::Ptr &ptr) const;
virtual int getBaseFightRating(const MWWorld::ConstPtr &ptr) const; virtual int getBaseFightRating(const MWWorld::ConstPtr &ptr) const;
virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const;

View file

@ -1164,7 +1164,8 @@ namespace MWClass
// TODO: This function is called several times per frame for each NPC. // TODO: This function is called several times per frame for each NPC.
// It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats. // It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats.
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead())
return 0.f; return 0.f;
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -1213,7 +1214,8 @@ namespace MWClass
return 0.f; return 0.f;
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead())
return 0.f; return 0.f;
const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData()); const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
@ -1622,14 +1624,6 @@ namespace MWClass
} }
} }
void Npc::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
store.restock(list, ptr, ptr.getCellRef().getRefId());
}
int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();

View file

@ -168,8 +168,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const;
virtual void restock (const MWWorld::Ptr& ptr) const;
virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const;
virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const;

View file

@ -941,6 +941,9 @@ public:
if (!mBook) if (!mBook)
return; return;
if (mPage >= mBook->mPages.size())
return;
dirtyFocusItem (); dirtyFocusItem ();
mFocusItem = 0; mFocusItem = 0;
@ -952,6 +955,9 @@ public:
if (!mBook) if (!mBook)
return; return;
if (mPage >= mBook->mPages.size())
return;
left -= mCroppedParent->getAbsoluteLeft (); left -= mCroppedParent->getAbsoluteLeft ();
top -= mCroppedParent->getAbsoluteTop (); top -= mCroppedParent->getAbsoluteTop ();
@ -988,6 +994,9 @@ public:
if (!mBook) if (!mBook)
return; return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse press coordinates aren't // work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are). // transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top); MyGUI::IntPoint pos (left, top);
@ -1013,6 +1022,9 @@ public:
if (!mBook) if (!mBook)
return; return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse release coordinates aren't // work around inconsistency in MyGUI where the mouse release coordinates aren't
// transformed by the current Layer (even though mouse *move* events are). // transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top); MyGUI::IntPoint pos (left, top);

View file

@ -273,6 +273,7 @@ namespace MWGui
if (!mPtr.isEmpty()) if (!mPtr.isEmpty())
MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); MWBase::Environment::get().getMechanicsManager()->onClose(mPtr);
resetReference();
} }
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)

View file

@ -52,22 +52,29 @@ namespace
namespace MWGui namespace MWGui
{ {
ContainerItemModel::ContainerItemModel(const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& worldItems) ContainerItemModel::ContainerItemModel(const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& worldItems)
: mItemSources(itemSources) : mWorldItems(worldItems)
, mWorldItems(worldItems) , mTrading(true)
{ {
assert (!mItemSources.empty()); assert (!itemSources.empty());
// Tie resolution lifetimes to the ItemModel
mItemSources.reserve(itemSources.size());
for(const MWWorld::Ptr& source : itemSources)
{
MWWorld::ContainerStore& store = source.getClass().getContainerStore(source);
mItemSources.push_back(std::make_pair(source, store.resolveTemporarily()));
}
} }
ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) : mTrading(false)
{ {
mItemSources.push_back(source); MWWorld::ContainerStore& store = source.getClass().getContainerStore(source);
mItemSources.push_back(std::make_pair(source, store.resolveTemporarily()));
} }
bool ContainerItemModel::allowedToUseItems() const bool ContainerItemModel::allowedToUseItems() const
{ {
if (mItemSources.size() == 0) if (mItemSources.empty())
return true; return true;
MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWWorld::Ptr ptr = MWMechanics::getPlayer();
@ -75,7 +82,7 @@ bool ContainerItemModel::allowedToUseItems() const
// Check if the player is allowed to use items from opened container // Check if the player is allowed to use items from opened container
MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager();
return mm->isAllowedToUse(ptr, mItemSources[0], victim); return mm->isAllowedToUse(ptr, mItemSources[0].first, victim);
} }
ItemStack ContainerItemModel::getItem (ModelIndex index) ItemStack ContainerItemModel::getItem (ModelIndex index)
@ -106,8 +113,9 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip)
{ {
const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; auto& source = mItemSources[0];
if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first);
if (item.mBase.getContainerStore() == &store)
throw std::runtime_error("Item to copy needs to be from a different container!"); throw std::runtime_error("Item to copy needs to be from a different container!");
/* /*
@ -118,10 +126,10 @@ MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count,
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset(); objectList->reset();
objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY; objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;
objectList->cell = *source.getCell()->getCell(); objectList->cell = *source.first.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::ADD; objectList->action = mwmp::BaseObjectList::ADD;
objectList->containerSubAction = mwmp::BaseObjectList::NONE; objectList->containerSubAction = mwmp::BaseObjectList::NONE;
mwmp::BaseObject baseObject = objectList->getBaseObjectFromPtr(source); mwmp::BaseObject baseObject = objectList->getBaseObjectFromPtr(source.first);
objectList->addContainerItem(baseObject, item.mBase, count, 0); objectList->addContainerItem(baseObject, item.mBase, count, 0);
objectList->addBaseObject(baseObject); objectList->addBaseObject(baseObject);
objectList->sendContainer(); objectList->sendContainer();
@ -146,9 +154,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
{ {
int toRemove = count; int toRemove = count;
for (MWWorld::Ptr& source : mItemSources) for (auto& source : mItemSources)
{ {
MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{ {
@ -164,16 +172,16 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
*/ */
mwmp::CurrentContainer *currentContainer = &mwmp::Main::get().getLocalPlayer()->currentContainer; mwmp::CurrentContainer *currentContainer = &mwmp::Main::get().getLocalPlayer()->currentContainer;
if (currentContainer->refNum != source.getCellRef().getRefNum().mIndex || if (currentContainer->refNum != source.first.getCellRef().getRefNum().mIndex ||
currentContainer->mpNum != source.getCellRef().getMpNum()) currentContainer->mpNum != source.first.getCellRef().getMpNum())
{ {
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset(); objectList->reset();
objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY; objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;
objectList->cell = *source.getCell()->getCell(); objectList->cell = *source.first.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::REMOVE; objectList->action = mwmp::BaseObjectList::REMOVE;
objectList->containerSubAction = mwmp::BaseObjectList::NONE; objectList->containerSubAction = mwmp::BaseObjectList::NONE;
mwmp::BaseObject baseObject = objectList->getBaseObjectFromPtr(source); mwmp::BaseObject baseObject = objectList->getBaseObjectFromPtr(source.first);
objectList->addContainerItem(baseObject, *it, it->getRefData().getCount(), toRemove); objectList->addContainerItem(baseObject, *it, it->getRefData().getCount(), toRemove);
objectList->addBaseObject(baseObject); objectList->addBaseObject(baseObject);
objectList->sendContainer(); objectList->sendContainer();
@ -181,7 +189,14 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
toRemove -= it->getRefData().getCount(); toRemove -= it->getRefData().getCount();
} }
else else
toRemove -= store.remove(*it, toRemove, source); {
int quantity = it->mRef->mData.getCount(false);
// If this is a restocking quantity, just don't remove it
if (quantity < 0 && mTrading)
toRemove += quantity;
else
toRemove -= store.remove(*it, toRemove, source.first);
}
/* /*
End of tes3mp change (major) End of tes3mp change (major)
*/ */
@ -229,9 +244,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
void ContainerItemModel::update() void ContainerItemModel::update()
{ {
mItems.clear(); mItems.clear();
for (MWWorld::Ptr& source : mItemSources) for (auto& source : mItemSources)
{ {
MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{ {
@ -285,7 +300,7 @@ bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count)
if (mItemSources.empty()) if (mItemSources.empty())
return false; return false;
MWWorld::Ptr target = mItemSources[0]; MWWorld::Ptr target = mItemSources[0].first;
if (target.getTypeName() != typeid(ESM::Container).name()) if (target.getTypeName() != typeid(ESM::Container).name())
return true; return true;
@ -315,7 +330,7 @@ bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count)
if (mItemSources.empty()) if (mItemSources.empty())
return false; return false;
MWWorld::Ptr target = mItemSources[0]; MWWorld::Ptr target = mItemSources[0].first;
// Looting a dead corpse is considered OK // Looting a dead corpse is considered OK
if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead())

View file

@ -1,8 +1,13 @@
#ifndef MWGUI_CONTAINER_ITEM_MODEL_H #ifndef MWGUI_CONTAINER_ITEM_MODEL_H
#define MWGUI_CONTAINER_ITEM_MODEL_H #define MWGUI_CONTAINER_ITEM_MODEL_H
#include <utility>
#include <vector>
#include "itemmodel.hpp" #include "itemmodel.hpp"
#include "../mwworld/containerstore.hpp"
namespace MWGui namespace MWGui
{ {
@ -32,9 +37,9 @@ namespace MWGui
virtual void update(); virtual void update();
private: private:
std::vector<MWWorld::Ptr> mItemSources; std::vector<std::pair<MWWorld::Ptr, MWWorld::ResolutionHandle>> mItemSources;
std::vector<MWWorld::Ptr> mWorldItems; std::vector<MWWorld::Ptr> mWorldItems;
const bool mTrading;
std::vector<ItemStack> mItems; std::vector<ItemStack> mItems;
}; };

View file

@ -817,7 +817,8 @@ namespace MWGui
return; return;
const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return; return;
ItemModel::ModelIndex selected = -1; ItemModel::ModelIndex selected = -1;

View file

@ -414,7 +414,8 @@ namespace MWGui
|| playerStats.getKnockedDown() || playerStats.getKnockedDown()
|| playerStats.getHitRecovery(); || playerStats.getHitRecovery();
bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
bool isReturnNeeded = (!godmode && playerStats.isParalyzed()) || playerStats.isDead();
if (isReturnNeeded && key->type != Type_Item) if (isReturnNeeded && key->type != Type_Item)
{ {

View file

@ -272,8 +272,9 @@ namespace MWGui
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player))
return; return;
bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return; return;
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), "")); mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), ""));

View file

@ -119,7 +119,7 @@ namespace MWGui
mBorrowedToUs.clear(); mBorrowedToUs.clear();
} }
std::vector<ItemStack> TradeItemModel::getItemsBorrowedToUs() const std::vector<ItemStack> TradeItemModel::getItemsBorrowedToUs() const
{ {
return mBorrowedToUs; return mBorrowedToUs;
} }

View file

@ -40,7 +40,7 @@ namespace MWGui
/// and removing weight for items we've lent to someone else. /// and removing weight for items we've lent to someone else.
void adjustEncumbrance (float& encumbrance); void adjustEncumbrance (float& encumbrance);
std::vector<ItemStack> getItemsBorrowedToUs(); const std::vector<ItemStack> getItemsBorrowedToUs() const;
private: private:
void borrowImpl(const ItemStack& item, std::vector<ItemStack>& out); void borrowImpl(const ItemStack& item, std::vector<ItemStack>& out);

View file

@ -111,49 +111,6 @@ namespace MWGui
setCoord(400, 0, 400, 300); setCoord(400, 0, 400, 300);
} }
void TradeWindow::restock()
{
// Restock items on the actor inventory
/*
Start of tes3mp change (major)
Don't restock here and instead send an ID_OBJECT_RESTOCK packet to the
server requesting permission for a restock
*/
//mPtr.getClass().restock(mPtr);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectGeneric(mPtr);
objectList->sendObjectRestock();
/*
End of tes3mp change (major)
*/
// Also restock any containers owned by this merchant, which are also available to buy in the trade window
std::vector<MWWorld::Ptr> itemSources;
MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources);
for (MWWorld::Ptr& source : itemSources)
{
/*
Start of tes3mp change (major)
Don't restock here and instead send an ID_OBJECT_RESTOCK packet to the
server requesting permission for a restock
*/
//source.getClass().restock(source);
objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectGeneric(source);
objectList->sendObjectRestock();
/*
End of tes3mp change (major)
*/
}
}
void TradeWindow::setPtr(const MWWorld::Ptr& actor) void TradeWindow::setPtr(const MWWorld::Ptr& actor)
{ {
mPtr = actor; mPtr = actor;
@ -162,10 +119,10 @@ namespace MWGui
mCurrentMerchantOffer = 0; mCurrentMerchantOffer = 0;
std::vector<MWWorld::Ptr> itemSources; std::vector<MWWorld::Ptr> itemSources;
// Important: actor goes first, so purchased items come out of the actor's pocket first
itemSources.push_back(actor);
MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources);
// Important: actor goes last, so that items purchased by the merchant go into his inventory
itemSources.push_back(actor);
std::vector<MWWorld::Ptr> worldItems; std::vector<MWWorld::Ptr> worldItems;
MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems);
@ -322,8 +279,8 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// were there any items traded at all? // were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& merchantBought = mTradeModel->getItemsBorrowedToUs();
if (playerBought.empty() && merchantBought.empty()) if (playerBought.empty() && merchantBought.empty())
{ {
// user notification // user notification
@ -354,7 +311,7 @@ namespace MWGui
} }
// check if the player is attempting to sell back an item stolen from this actor // check if the player is attempting to sell back an item stolen from this actor
for (ItemStack& itemStack : merchantBought) for (const ItemStack& itemStack : merchantBought)
{ {
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr))
{ {
@ -421,8 +378,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up");
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
restock();
} }
void TradeWindow::onAccept(MyGUI::EditBox *sender) void TradeWindow::onAccept(MyGUI::EditBox *sender)
@ -536,7 +491,7 @@ namespace MWGui
// connected to buying and selling the same item. // connected to buying and selling the same item.
// This value has been determined by researching the limitations of the vanilla formula // This value has been determined by researching the limitations of the vanilla formula
// and may not be sufficient if getBarterOffer behavior has been changed. // and may not be sufficient if getBarterOffer behavior has been changed.
std::vector<ItemStack> playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& playerBorrowed = playerTradeModel->getItemsBorrowedToUs();
for (const ItemStack& itemStack : playerBorrowed) for (const ItemStack& itemStack : playerBorrowed)
{ {
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -545,7 +500,7 @@ namespace MWGui
merchantOffer -= std::max(cap, buyingPrice); merchantOffer -= std::max(cap, buyingPrice);
} }
std::vector<ItemStack> merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& merchantBorrowed = mTradeModel->getItemsBorrowedToUs();
for (const ItemStack& itemStack : merchantBorrowed) for (const ItemStack& itemStack : merchantBorrowed)
{ {
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -590,4 +545,9 @@ namespace MWGui
mTradeModel = nullptr; mTradeModel = nullptr;
mSortModel = nullptr; mSortModel = nullptr;
} }
void TradeWindow::onClose()
{
resetReference();
}
} }

View file

@ -29,6 +29,7 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor); void setPtr(const MWWorld::Ptr& actor);
virtual void onClose() override;
void onFrame(float dt); void onFrame(float dt);
void clear() { resetReference(); } void clear() { resetReference(); }
@ -111,8 +112,6 @@ namespace MWGui
virtual void onReferenceUnavailable(); virtual void onReferenceUnavailable();
int getMerchantGold(); int getMerchantGold();
void restock();
}; };
} }

View file

@ -86,7 +86,7 @@ namespace MWGui
// Add price for the travelling followers // Add price for the travelling followers
std::set<MWWorld::Ptr> followers; std::set<MWWorld::Ptr> followers;
MWWorld::ActionTeleport::getFollowersToTeleport(player, followers); MWWorld::ActionTeleport::getFollowers(player, followers);
// Apply followers cost, unlike vanilla the first follower doesn't travel for free // Apply followers cost, unlike vanilla the first follower doesn't travel for free
price *= 1 + static_cast<int>(followers.size()); price *= 1 + static_cast<int>(followers.size());

View file

@ -963,6 +963,7 @@ namespace MWMechanics
{ {
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects(); const MagicEffects &effects = creatureStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
bool wasDead = creatureStats.isDead(); bool wasDead = creatureStats.isDead();
@ -1014,8 +1015,11 @@ namespace MWMechanics
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
{ {
DynamicStat<float> stat = creatureStats.getDynamic(i); DynamicStat<float> stat = creatureStats.getDynamic(i);
stat.setCurrentModifier(effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude() - float fortify = effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude();
effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(), float drain = 0.f;
if (!godmode)
drain = effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude();
stat.setCurrentModifier(fortify - drain,
// Magicka can be decreased below zero due to a fortify effect wearing off // Magicka can be decreased below zero due to a fortify effect wearing off
// Fatigue can be decreased below zero meaning the actor will be knocked out // Fatigue can be decreased below zero meaning the actor will be knocked out
i == 1 || i == 2); i == 1 || i == 2);
@ -1027,9 +1031,14 @@ namespace MWMechanics
for(int i = 0;i < ESM::Attribute::Length;++i) for(int i = 0;i < ESM::Attribute::Length;++i)
{ {
AttributeValue stat = creatureStats.getAttribute(i); AttributeValue stat = creatureStats.getAttribute(i);
stat.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude() - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude();
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() - float drain = 0.f, absorb = 0.f;
effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude())); if (!godmode)
{
drain = effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude();
absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude();
}
stat.setModifier(static_cast<int>(fortify - drain - absorb));
creatureStats.setAttribute(i, stat); creatureStats.setAttribute(i, stat);
} }
@ -1278,14 +1287,20 @@ namespace MWMechanics
{ {
NpcStats &npcStats = ptr.getClass().getNpcStats(ptr); NpcStats &npcStats = ptr.getClass().getNpcStats(ptr);
const MagicEffects &effects = npcStats.getMagicEffects(); const MagicEffects &effects = npcStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
// skills // skills
for(int i = 0;i < ESM::Skill::Length;++i) for(int i = 0;i < ESM::Skill::Length;++i)
{ {
SkillValue& skill = npcStats.getSkill(i); SkillValue& skill = npcStats.getSkill(i);
skill.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude();
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() - float drain = 0.f, absorb = 0.f;
effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude())); if (!godmode)
{
drain = effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude();
absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude();
}
skill.setModifier(static_cast<int>(fortify - drain - absorb));
} }
} }
@ -1789,8 +1804,9 @@ namespace MWMechanics
void Actors::predictAndAvoidCollisions() void Actors::predictAndAvoidCollisions()
{ {
const float minGap = 10.f; const float minGap = 10.f;
const float maxDistToCheck = 100.f; const float maxDistForPartialAvoiding = 200.f;
const float maxTimeToCheck = 1.f; const float maxDistForStrictAvoiding = 100.f;
const float maxTimeToCheck = 2.0f;
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game"); static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
@ -1801,9 +1817,15 @@ namespace MWMechanics
if (ptr == player) if (ptr == player)
continue; // Don't interfere with player controls. continue; // Don't interfere with player controls.
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
if (maxSpeed == 0.0)
continue; // Can't move, so there is no sense to predict collisions.
Movement& movement = ptr.getClass().getMovementSettings(ptr); Movement& movement = ptr.getClass().getMovementSettings(ptr);
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
bool isMoving = origMovement.length2() > 0.01; bool isMoving = origMovement.length2() > 0.01;
if (movement.mPosition[1] < 0)
continue; // Actors can not see others when move backward.
// Moving NPCs always should avoid collisions. // Moving NPCs always should avoid collisions.
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
@ -1831,11 +1853,11 @@ namespace MWMechanics
if (!shouldAvoidCollision) if (!shouldAvoidCollision)
continue; continue;
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
osg::Vec2f baseSpeed = origMovement * maxSpeed; osg::Vec2f baseSpeed = origMovement * maxSpeed;
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
float baseRotZ = ptr.getRefData().getPosition().rot[2]; float baseRotZ = ptr.getRefData().getPosition().rot[2];
osg::Vec3f halfExtents = world->getHalfExtents(ptr); osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
float timeToCollision = maxTimeToCheck; float timeToCollision = maxTimeToCheck;
osg::Vec2f movementCorrection(0, 0); osg::Vec2f movementCorrection(0, 0);
@ -1851,9 +1873,10 @@ namespace MWMechanics
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
float dist = deltaPos.length();
// Ignore actors which are not close enough or come from behind. // Ignore actors which are not close enough or come from behind.
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0) if (dist > maxDistToCheck || relPos.y() < 0)
continue; continue;
// Don't check for a collision if vertical distance is greater then the actor's height. // Don't check for a collision if vertical distance is greater then the actor's height.
@ -1888,16 +1911,20 @@ namespace MWMechanics
timeToCollision = t; timeToCollision = t;
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y()); angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
osg::Vec2f posAtT = relPos + relSpeed * t; osg::Vec2f posAtT = relPos + relSpeed * t;
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * maxSpeed); float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed);
coef *= osg::clampBetween((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f);
movementCorrection = posAtT * coef; movementCorrection = posAtT * coef;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location. if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
movementCorrection.y() = std::max(0.f, movementCorrection.y()); // In case of dead body still try to go around (it looks natural), but reduce the correction twice.
movementCorrection.y() *= 0.5f;
} }
if (timeToCollision < maxTimeToCheck) if (timeToCollision < maxTimeToCheck)
{ {
// Try to evade the nearest collision. // Try to evade the nearest collision.
osg::Vec2f newMovement = origMovement + movementCorrection; osg::Vec2f newMovement = origMovement + movementCorrection;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
newMovement.y() = std::max(newMovement.y(), 0.f);
if (isMoving) if (isMoving)
{ // Keep the original speed. { // Keep the original speed.
newMovement.normalize(); newMovement.normalize();
@ -1948,6 +1975,7 @@ namespace MWMechanics
if (!playerHitAttemptActor.isInCell()) if (!playerHitAttemptActor.isInCell())
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
} }
bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
// AI and magic effects update // AI and magic effects update
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
@ -2182,7 +2210,7 @@ namespace MWMechanics
iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor);
const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead();
if (!isDead && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) if (!isDead && (!godmode || !isPlayer) && iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
ctrl->skipAnim(); ctrl->skipAnim();
// Handle player last, in case a cell transition occurs by casting a teleportation spell // Handle player last, in case a cell transition occurs by casting a teleportation spell

View file

@ -308,24 +308,24 @@ namespace MWMechanics
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
if (isRangedCombat)
{
// rotate actor taking into account target movement direction and projectile speed
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
lastTargetPos = vTargetPos;
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
}
else
{
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
}
if (storage.mReadyToAttack) if (storage.mReadyToAttack)
{ {
if (isRangedCombat)
{
// rotate actor taking into account target movement direction and projectile speed
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
lastTargetPos = vTargetPos;
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
}
else
{
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
}
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
// start new attack // start new attack
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);

View file

@ -2087,7 +2087,7 @@ void CharacterController::update(float duration, bool animationOnly)
else if(!cls.getCreatureStats(mPtr).isDead()) else if(!cls.getCreatureStats(mPtr).isDead())
{ {
bool onground = world->isOnGround(mPtr); bool onground = world->isOnGround(mPtr);
bool incapacitated = (cls.getCreatureStats(mPtr).isParalyzed() || cls.getCreatureStats(mPtr).getKnockedDown()); bool incapacitated = ((!godmode && cls.getCreatureStats(mPtr).isParalyzed()) || cls.getCreatureStats(mPtr).getKnockedDown());
bool inwater = world->isSwimming(mPtr); bool inwater = world->isSwimming(mPtr);
bool flying = world->isFlying(mPtr); bool flying = world->isFlying(mPtr);
bool solid = world->isActorCollisionEnabled(mPtr); bool solid = world->isActorCollisionEnabled(mPtr);

View file

@ -19,14 +19,14 @@ namespace MWMechanics
{ {
/// @return ID of resulting item, or empty if none /// @return ID of resulting item, or empty if none
inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature) inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Seed& seed = Misc::Rng::getSeed())
{ {
const std::vector<ESM::LevelledListBase::LevelItem>& items = levItem->mList; const std::vector<ESM::LevelledListBase::LevelItem>& items = levItem->mList;
const MWWorld::Ptr& player = getPlayer(); const MWWorld::Ptr& player = getPlayer();
int playerLevel = player.getClass().getCreatureStats(player).getLevel(); int playerLevel = player.getClass().getCreatureStats(player).getLevel();
if (Misc::Rng::roll0to99() < levItem->mChanceNone) if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone)
return std::string(); return std::string();
std::vector<std::string> candidates; std::vector<std::string> candidates;
@ -55,7 +55,7 @@ namespace MWMechanics
} }
if (candidates.empty()) if (candidates.empty())
return std::string(); return std::string();
std::string item = candidates[Misc::Rng::rollDice(candidates.size())]; std::string item = candidates[Misc::Rng::rollDice(candidates.size(), seed)];
// Vanilla doesn't fail on nonexistent items in levelled lists // Vanilla doesn't fail on nonexistent items in levelled lists
if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item)))
@ -74,9 +74,9 @@ namespace MWMechanics
else else
{ {
if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name())
return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false); return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false, seed);
else else
return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true); return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true, seed);
} }
} }

View file

@ -1108,6 +1108,7 @@ namespace MWMechanics
void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer)
{ {
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
MWWorld::ContainerStore& containerStore = targetContainer.getClass().getContainerStore(targetContainer);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{ {
StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId()));
@ -1128,7 +1129,7 @@ namespace MWMechanics
int toMove = it->getRefData().getCount() - itemCount; int toMove = it->getRefData().getCount() - itemCount;
targetContainer.getClass().getContainerStore(targetContainer).add(*it, toMove, targetContainer); containerStore.add(*it, toMove, targetContainer);
store.remove(*it, toMove, player); store.remove(*it, toMove, player);
} }
// TODO: unhardcode the locklevel // TODO: unhardcode the locklevel

View file

@ -103,7 +103,7 @@ namespace MWMechanics
resultMessage = "#{sLockFail}"; resultMessage = "#{sLockFail}";
} }
lockpick.getCellRef().setCharge(uses-1); lockpick.getCellRef().setCharge(--uses);
if (!uses) if (!uses)
lockpick.getContainerStore()->remove(lockpick, 1, mActor); lockpick.getContainerStore()->remove(lockpick, 1, mActor);
} }
@ -171,7 +171,7 @@ namespace MWMechanics
resultMessage = "#{sTrapFail}"; resultMessage = "#{sTrapFail}";
} }
probe.getCellRef().setCharge(uses-1); probe.getCellRef().setCharge(--uses);
if (!uses) if (!uses)
probe.getContainerStore()->remove(probe, 1, mActor); probe.getContainerStore()->remove(probe, 1, mActor);
} }

View file

@ -46,7 +46,7 @@ namespace MWMechanics
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{ {
if (spellId.empty() || caster == target || !target.getClass().isActor()) if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor())
return false; return false;
CreatureStats& stats = target.getClass().getCreatureStats(target); CreatureStats& stats = target.getClass().getCreatureStats(target);

View file

@ -80,11 +80,14 @@ namespace MWMechanics
return false; return false;
bool receivedMagicDamage = false; bool receivedMagicDamage = false;
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
switch (effectKey.mId) switch (effectKey.mId)
{ {
case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::DamageAttribute:
{ {
if (godmode)
break;
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.damage(magnitude); attr.damage(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr); creatureStats.setAttribute(effectKey.mArg, attr);
@ -103,6 +106,8 @@ namespace MWMechanics
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
break; break;
case ESM::MagicEffect::DamageHealth: case ESM::MagicEffect::DamageHealth:
if (godmode)
break;
receivedMagicDamage = true; receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
break; break;
@ -110,25 +115,32 @@ namespace MWMechanics
case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue: case ESM::MagicEffect::DamageFatigue:
{ {
if (godmode)
break;
int index = effectKey.mId-ESM::MagicEffect::DamageHealth; int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
break; break;
} }
case ESM::MagicEffect::AbsorbHealth: case ESM::MagicEffect::AbsorbHealth:
if (magnitude > 0.f) if (!godmode || magnitude <= 0)
receivedMagicDamage = true; {
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); if (magnitude > 0.f)
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
}
break; break;
case ESM::MagicEffect::AbsorbMagicka: case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue: case ESM::MagicEffect::AbsorbFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); if (!godmode || magnitude <= 0)
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break; break;
case ESM::MagicEffect::DisintegrateArmor: case ESM::MagicEffect::DisintegrateArmor:
{ {
if (godmode)
break;
static const std::array<int, 9> priorities static const std::array<int, 9> priorities
{ {
MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft,
@ -150,13 +162,14 @@ namespace MWMechanics
break; break;
} }
case ESM::MagicEffect::DisintegrateWeapon: case ESM::MagicEffect::DisintegrateWeapon:
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); if (!godmode)
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
break; break;
case ESM::MagicEffect::SunDamage: case ESM::MagicEffect::SunDamage:
{ {
// isInCell shouldn't be needed, but updateActor called during game start // isInCell shouldn't be needed, but updateActor called during game start
if (!actor.isInCell() || !actor.getCell()->isExterior()) if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode)
break; break;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
@ -181,6 +194,8 @@ namespace MWMechanics
case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::Poison: case ESM::MagicEffect::Poison:
{ {
if (godmode)
break;
adjustDynamicStat(creatureStats, 0, -magnitude); adjustDynamicStat(creatureStats, 0, -magnitude);
receivedMagicDamage = true; receivedMagicDamage = true;
break; break;
@ -191,6 +206,8 @@ namespace MWMechanics
{ {
if (!actor.getClass().isNpc()) if (!actor.getClass().isNpc())
break; break;
if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill)
break;
NpcStats &npcStats = actor.getClass().getNpcStats(actor); NpcStats &npcStats = actor.getClass().getNpcStats(actor);
SkillValue& skill = npcStats.getSkill(effectKey.mArg); SkillValue& skill = npcStats.getSkill(effectKey.mArg);
if (effectKey.mId == ESM::MagicEffect::RestoreSkill) if (effectKey.mId == ESM::MagicEffect::RestoreSkill)

View file

@ -239,7 +239,7 @@ namespace MWMechanics
/* short group */ "", /* short group */ "",
/* long group */ "", /* long group */ "",
/* sound ID */ "Item Ammo", /* sound ID */ "Item Ammo",
/* attach bone */ "ArrowBone", /* attach bone */ "Bip01 Arrow",
/* sheath bone */ "", /* sheath bone */ "",
/* usage skill */ ESM::Skill::Marksman, /* usage skill */ ESM::Skill::Marksman,
/* weapon class*/ ESM::WeaponType::Ammo, /* weapon class*/ ESM::WeaponType::Ammo,

View file

@ -136,6 +136,10 @@ void ObjectList::addEntireContainer(const MWWorld::Ptr& ptr)
mwmp::BaseObject baseObject = getBaseObjectFromPtr(ptr); mwmp::BaseObject baseObject = getBaseObjectFromPtr(ptr);
// If the container store has not been populated with items yet, handle that now
if (!containerStore.isResolved())
containerStore.resolve();
for (const auto itemPtr : containerStore) for (const auto itemPtr : containerStore)
{ {
addContainerItem(baseObject, itemPtr, itemPtr.getRefData().getCount(), itemPtr.getRefData().getCount()); addContainerItem(baseObject, itemPtr, itemPtr.getRefData().getCount(), itemPtr.getRefData().getCount());
@ -184,7 +188,10 @@ void ObjectList::editContainers(MWWorld::CellStore* cellStore)
// If we are setting the entire contents, clear the current ones // If we are setting the entire contents, clear the current ones
if (action == BaseObjectList::SET) if (action == BaseObjectList::SET)
{
containerStore.setResolved(true);
containerStore.clear(); containerStore.clear();
}
bool isLocalDrag = isLocalEvent && containerSubAction == BaseObjectList::DRAG; bool isLocalDrag = isLocalEvent && containerSubAction == BaseObjectList::DRAG;
bool isLocalTakeAll = isLocalEvent && containerSubAction == BaseObjectList::TAKE_ALL; bool isLocalTakeAll = isLocalEvent && containerSubAction == BaseObjectList::TAKE_ALL;
@ -724,7 +731,7 @@ void ObjectList::restockObjects(MWWorld::CellStore* cellStore)
LOG_APPEND(TimedLog::LOG_VERBOSE, "-- Found %s %i-%i", ptrFound.getCellRef().getRefId().c_str(), LOG_APPEND(TimedLog::LOG_VERBOSE, "-- Found %s %i-%i", ptrFound.getCellRef().getRefId().c_str(),
ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum()); ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum());
ptrFound.getClass().restock(ptrFound); //ptrFound.getClass().restock(ptrFound);
reset(); reset();
packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY; packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;

View file

@ -25,37 +25,35 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "mtphysics.hpp"
namespace MWPhysics namespace MWPhysics
{ {
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world) Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mCanWaterWalk(false), mWalkingOnWater(false) : mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true) , mInternalCollisionMode(true)
, mExternalCollisionMode(true) , mExternalCollisionMode(true)
, mCollisionWorld(world) , mTaskScheduler(scheduler)
{ {
mPtr = ptr; mPtr = ptr;
mHalfExtents = shape->mCollisionBoxHalfExtents;
mMeshTranslation = shape->mCollisionBoxTranslate;
// We can not create actor without collisions - he will fall through the ground. // We can not create actor without collisions - he will fall through the ground.
// In this case we should autogenerate collision box based on mesh shape // In this case we should autogenerate collision box based on mesh shape
// (NPCs have bodyparts and use a different approach) // (NPCs have bodyparts and use a different approach)
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f) if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
{ {
const Resource::BulletShape* collisionShape = shape.get(); if (shape->mCollisionShape)
if (collisionShape && collisionShape->mCollisionShape)
{ {
btTransform transform; btTransform transform;
transform.setIdentity(); transform.setIdentity();
btVector3 min; btVector3 min;
btVector3 max; btVector3 max;
collisionShape->mCollisionShape->getAabb(transform, min, max); shape->mCollisionShape->getAabb(transform, min, max);
mHalfExtents.x() = (max[0] - min[0])/2.f; mHalfExtents.x() = (max[0] - min[0])/2.f;
mHalfExtents.y() = (max[1] - min[1])/2.f; mHalfExtents.y() = (max[1] - min[1])/2.f;
mHalfExtents.z() = (max[2] - min[2])/2.f; mHalfExtents.z() = (max[2] - min[2])/2.f;
@ -113,17 +111,19 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
/* /*
End of tes3mp addition End of tes3mp addition
*/ */
commitPositionChange();
} }
Actor::~Actor() Actor::~Actor()
{ {
if (mCollisionObject.get()) if (mCollisionObject)
mCollisionWorld->removeCollisionObject(mCollisionObject.get()); mTaskScheduler->removeCollisionObject(mCollisionObject.get());
} }
void Actor::enableCollisionMode(bool collision) void Actor::enableCollisionMode(bool collision)
{ {
mInternalCollisionMode = collision; mInternalCollisionMode.store(collision, std::memory_order_release);
} }
void Actor::enableCollisionBody(bool collision) void Actor::enableCollisionBody(bool collision)
@ -137,16 +137,15 @@ void Actor::enableCollisionBody(bool collision)
void Actor::addCollisionMask(int collisionMask) void Actor::addCollisionMask(int collisionMask)
{ {
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
} }
void Actor::updateCollisionMask() void Actor::updateCollisionMask()
{ {
mCollisionWorld->removeCollisionObject(mCollisionObject.get()); mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask());
addCollisionMask(getCollisionMask());
} }
int Actor::getCollisionMask() int Actor::getCollisionMask() const
{ {
int collisionMask = CollisionType_World | CollisionType_HeightMap; int collisionMask = CollisionType_World | CollisionType_HeightMap;
if (mExternalCollisionMode) if (mExternalCollisionMode)
@ -154,58 +153,91 @@ int Actor::getCollisionMask()
if (mCanWaterWalk) if (mCanWaterWalk)
collisionMask |= CollisionType_Water; collisionMask |= CollisionType_Water;
return collisionMask; return collisionMask;
} }
void Actor::updatePosition() void Actor::updatePosition()
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
mPosition = position; mPosition = position;
mPreviousPosition = position; mPreviousPosition = position;
mTransformUpdatePending = true;
updateCollisionObjectPosition(); updateCollisionObjectPosition();
} }
void Actor::updateCollisionObjectPosition() void Actor::updateCollisionObjectPosition()
{ {
btTransform tr = mCollisionObject->getWorldTransform();
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition; osg::Vec3f newPosition = scaledTranslation + mPosition;
tr.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mCollisionObject->setWorldTransform(tr); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
}
void Actor::commitPositionChange()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (mScaleUpdatePending)
{
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
mScaleUpdatePending = false;
}
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
} }
osg::Vec3f Actor::getCollisionObjectPosition() const osg::Vec3f Actor::getCollisionObjectPosition() const
{ {
return Misc::Convert::toOsg(mCollisionObject->getWorldTransform().getOrigin()); std::unique_lock<std::mutex> lock(mPositionMutex);
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
} }
void Actor::setPosition(const osg::Vec3f &position) void Actor::setPosition(const osg::Vec3f &position, bool updateCollisionObject)
{ {
mPreviousPosition = mPosition; std::unique_lock<std::mutex> lock(mPositionMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
else
{
mPreviousPosition = mPosition;
mPosition = position; mPosition = position;
updateCollisionObjectPosition(); if (updateCollisionObject)
{
updateCollisionObjectPosition();
mCollisionObject->setWorldTransform(mLocalTransform);
}
}
} }
osg::Vec3f Actor::getPosition() const osg::Vec3f Actor::getPosition() const
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
return mPosition; return mPosition;
} }
osg::Vec3f Actor::getPreviousPosition() const osg::Vec3f Actor::getPreviousPosition() const
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
return mPreviousPosition; return mPreviousPosition;
} }
void Actor::updateRotation () void Actor::updateRotation ()
{ {
btTransform tr = mCollisionObject->getWorldTransform(); std::unique_lock<std::mutex> lock(mPositionMutex);
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
return;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
tr.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(tr);
mTransformUpdatePending = true;
updateCollisionObjectPosition(); updateCollisionObjectPosition();
} }
@ -216,32 +248,37 @@ bool Actor::isRotationallyInvariant() const
void Actor::updateScale() void Actor::updateScale()
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
float scale = mPtr.getCellRef().getScale(); float scale = mPtr.getCellRef().getScale();
osg::Vec3f scaleVec(scale,scale,scale); osg::Vec3f scaleVec(scale,scale,scale);
mPtr.getClass().adjustScale(mPtr, scaleVec, false); mPtr.getClass().adjustScale(mPtr, scaleVec, false);
mScale = scaleVec; mScale = scaleVec;
mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); mScaleUpdatePending = true;
scaleVec = osg::Vec3f(scale,scale,scale); scaleVec = osg::Vec3f(scale,scale,scale);
mPtr.getClass().adjustScale(mPtr, scaleVec, true); mPtr.getClass().adjustScale(mPtr, scaleVec, true);
mRenderingScale = scaleVec; mRenderingScale = scaleVec;
mTransformUpdatePending = true;
updateCollisionObjectPosition(); updateCollisionObjectPosition();
} }
osg::Vec3f Actor::getHalfExtents() const osg::Vec3f Actor::getHalfExtents() const
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mScale); return osg::componentMultiply(mHalfExtents, mScale);
} }
osg::Vec3f Actor::getOriginalHalfExtents() const osg::Vec3f Actor::getOriginalHalfExtents() const
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
return mHalfExtents; return mHalfExtents;
} }
osg::Vec3f Actor::getRenderingHalfExtents() const osg::Vec3f Actor::getRenderingHalfExtents() const
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mRenderingScale); return osg::componentMultiply(mHalfExtents, mRenderingScale);
} }
@ -252,26 +289,27 @@ void Actor::setInertialForce(const osg::Vec3f &force)
void Actor::setOnGround(bool grounded) void Actor::setOnGround(bool grounded)
{ {
mOnGround = grounded; mOnGround.store(grounded, std::memory_order_release);
} }
void Actor::setOnSlope(bool slope) void Actor::setOnSlope(bool slope)
{ {
mOnSlope = slope; mOnSlope.store(slope, std::memory_order_release);
} }
bool Actor::isWalkingOnWater() const bool Actor::isWalkingOnWater() const
{ {
return mWalkingOnWater; return mWalkingOnWater.load(std::memory_order_acquire);
} }
void Actor::setWalkingOnWater(bool walkingOnWater) void Actor::setWalkingOnWater(bool walkingOnWater)
{ {
mWalkingOnWater = walkingOnWater; mWalkingOnWater.store(walkingOnWater, std::memory_order_release);
} }
void Actor::setCanWaterWalk(bool waterWalk) void Actor::setCanWaterWalk(bool waterWalk)
{ {
std::unique_lock<std::mutex> lock(mPositionMutex);
if (waterWalk != mCanWaterWalk) if (waterWalk != mCanWaterWalk)
{ {
mCanWaterWalk = waterWalk; mCanWaterWalk = waterWalk;

View file

@ -1,15 +1,17 @@
#ifndef OPENMW_MWPHYSICS_ACTOR_H #ifndef OPENMW_MWPHYSICS_ACTOR_H
#define OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H
#include <atomic>
#include <memory> #include <memory>
#include <mutex>
#include "ptrholder.hpp" #include "ptrholder.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Quat> #include <osg/Quat>
#include <osg/ref_ptr> #include <osg/ref_ptr>
class btCollisionWorld;
class btCollisionShape; class btCollisionShape;
class btCollisionObject; class btCollisionObject;
class btConvexShape; class btConvexShape;
@ -21,12 +23,13 @@ namespace Resource
namespace MWPhysics namespace MWPhysics
{ {
class PhysicsTaskScheduler;
class Actor : public PtrHolder class Actor final : public PtrHolder
{ {
public: public:
Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world); Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler);
~Actor(); ~Actor() override;
/** /**
* Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry.
@ -35,7 +38,7 @@ namespace MWPhysics
bool getCollisionMode() const bool getCollisionMode() const
{ {
return mInternalCollisionMode; return mInternalCollisionMode.load(std::memory_order_acquire);
} }
btConvexShape* getConvexShape() const { return mConvexShape; } btConvexShape* getConvexShape() const { return mConvexShape; }
@ -60,6 +63,7 @@ namespace MWPhysics
void updatePosition(); void updatePosition();
void updateCollisionObjectPosition(); void updateCollisionObjectPosition();
void commitPositionChange();
/** /**
* Returns the half extents of the collision body (scaled according to collision scale) * Returns the half extents of the collision body (scaled according to collision scale)
@ -79,8 +83,9 @@ namespace MWPhysics
/** /**
* Store the current position into mPreviousPosition, then move to this position. * Store the current position into mPreviousPosition, then move to this position.
* Optionally, inform the physics engine about the change of position.
*/ */
void setPosition(const osg::Vec3f& position); void setPosition(const osg::Vec3f& position, bool updateCollisionObject=true);
osg::Vec3f getPosition() const; osg::Vec3f getPosition() const;
@ -110,14 +115,14 @@ namespace MWPhysics
bool getOnGround() const bool getOnGround() const
{ {
return mInternalCollisionMode && mOnGround; return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire);
} }
void setOnSlope(bool slope); void setOnSlope(bool slope);
bool getOnSlope() const bool getOnSlope() const
{ {
return mInternalCollisionMode && mOnSlope; return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire);
} }
btCollisionObject* getCollisionObject() const btCollisionObject* getCollisionObject() const
@ -136,10 +141,10 @@ namespace MWPhysics
/// Removes then re-adds the collision object to the dynamics world /// Removes then re-adds the collision object to the dynamics world
void updateCollisionMask(); void updateCollisionMask();
void addCollisionMask(int collisionMask); void addCollisionMask(int collisionMask);
int getCollisionMask(); int getCollisionMask() const;
bool mCanWaterWalk; bool mCanWaterWalk;
bool mWalkingOnWater; std::atomic<bool> mWalkingOnWater;
bool mRotationallyInvariant; bool mRotationallyInvariant;
@ -156,14 +161,18 @@ namespace MWPhysics
osg::Vec3f mRenderingScale; osg::Vec3f mRenderingScale;
osg::Vec3f mPosition; osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition; osg::Vec3f mPreviousPosition;
btTransform mLocalTransform;
bool mScaleUpdatePending;
bool mTransformUpdatePending;
mutable std::mutex mPositionMutex;
osg::Vec3f mForce; osg::Vec3f mForce;
bool mOnGround; std::atomic<bool> mOnGround;
bool mOnSlope; std::atomic<bool> mOnSlope;
bool mInternalCollisionMode; std::atomic<bool> mInternalCollisionMode;
bool mExternalCollisionMode; bool mExternalCollisionMode;
btCollisionWorld* mCollisionWorld; PhysicsTaskScheduler* mTaskScheduler;
Actor(const Actor&); Actor(const Actor&);
Actor& operator=(const Actor&); Actor& operator=(const Actor&);

View file

@ -10,18 +10,14 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/refdata.hpp" #include "../mwworld/refdata.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "physicssystem.hpp"
#include "stepper.hpp" #include "stepper.hpp"
#include "trace.h" #include "trace.h"
@ -78,24 +74,26 @@ namespace MWPhysics
return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset); return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset);
} }
osg::Vec3f MovementSolver::move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld, WorldFrameData& worldData)
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
{ {
const ESM::Position& refpos = ptr.getRefData().getPosition(); auto* physicActor = actor.mActorRaw;
auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures // Early-out for totally static creatures
// (Not sure if gravity should still apply?) // (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr)) if (!ptr.getClass().isMobile(ptr))
return position; return;
// Reset per-frame data // Reset per-frame data
physicActor->setWalkingOnWater(false); physicActor->setWalkingOnWater(false);
// Anything to collide with? // Anything to collide with?
if(!physicActor->getCollisionMode()) if(!physicActor->getCollisionMode())
{ {
return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
) * movement * time; ) * actor.mMovement * time;
return;
} }
const btCollisionObject *colobj = physicActor->getCollisionObject(); const btCollisionObject *colobj = physicActor->getCollisionObject();
@ -105,23 +103,23 @@ namespace MWPhysics
// That means the collision shape used for moving this actor is in a different spot than the collision shape // That means the collision shape used for moving this actor is in a different spot than the collision shape
// other actors are using to collide against this actor. // other actors are using to collide against this actor.
// While this is strictly speaking wrong, it's needed for MW compatibility. // While this is strictly speaking wrong, it's needed for MW compatibility.
position.z() += halfExtents.z(); actor.mPosition.z() += halfExtents.z();
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat(); static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
ActorTracer tracer; ActorTracer tracer;
osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f inertia = physicActor->getInertialForce();
osg::Vec3f velocity; osg::Vec3f velocity;
if (position.z() < swimlevel || isFlying) if (actor.mPosition.z() < swimlevel || actor.mFlying)
{ {
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
} }
else else
{ {
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope())) || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
@ -131,38 +129,16 @@ namespace MWPhysics
} }
// dead actors underwater will float to the surface, if the CharacterController tells us to do so // dead actors underwater will float to the surface, if the CharacterController tells us to do so
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
velocity = osg::Vec3f(0,0,1) * 25; velocity = osg::Vec3f(0,0,1) * 25;
if (ptr.getClass().getMovementSettings(ptr).mPosition[2]) if (actor.mWantJump)
{ actor.mDidJump = true;
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
}
// Decrease fatigue
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
{
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
}
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
}
// Now that we have the effective movement vector, apply wind forces to it // Now that we have the effective movement vector, apply wind forces to it
if (MWBase::Environment::get().getWorld()->isInStorm()) if (worldData.mIsInStorm)
{ {
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); osg::Vec3f stormDirection = worldData.mStormDirection;
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat(); static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
@ -170,7 +146,7 @@ namespace MWPhysics
Stepper stepper(collisionWorld, colobj); Stepper stepper(collisionWorld, colobj);
osg::Vec3f origVelocity = velocity; osg::Vec3f origVelocity = velocity;
osg::Vec3f newPosition = position; osg::Vec3f newPosition = actor.mPosition;
/* /*
* A loop to find newPosition using tracer, if successful different from the starting position. * A loop to find newPosition using tracer, if successful different from the starting position.
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
@ -182,7 +158,7 @@ namespace MWPhysics
osg::Vec3f nextpos = newPosition + velocity * remainingTime; osg::Vec3f nextpos = newPosition + velocity * remainingTime;
// If not able to fly, don't allow to swim up into the air // If not able to fly, don't allow to swim up into the air
if(!isFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
{ {
const osg::Vec3f down(0,0,-1); const osg::Vec3f down(0,0,-1);
velocity = slide(velocity, down); velocity = slide(velocity, down);
@ -235,7 +211,7 @@ namespace MWPhysics
if (result) if (result)
{ {
// don't let pure water creatures move out of water after stepMove // don't let pure water creatures move out of water after stepMove
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > waterlevel) if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition; newPosition = oldPosition;
} }
else else
@ -245,7 +221,7 @@ namespace MWPhysics
// Do not allow sliding upward if there is gravity. // Do not allow sliding upward if there is gravity.
// Stepping will have taken care of that. // Stepping will have taken care of that.
if(!(newPosition.z() < swimlevel || isFlying)) if(!(newPosition.z() < swimlevel || actor.mFlying))
newVelocity.z() = std::min(newVelocity.z(), 0.0f); newVelocity.z() = std::min(newVelocity.z(), 0.0f);
if ((newVelocity-velocity).length2() < 0.01) if ((newVelocity-velocity).length2() < 0.01)
@ -269,11 +245,11 @@ namespace MWPhysics
const btCollisionObject* standingOn = tracer.mHitObject; const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer()); PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder) if (ptrHolder)
standingCollisionTracker[ptr] = ptrHolder->getPtr(); actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true); physicActor->setWalkingOnWater(true);
if (!isFlying) if (!actor.mFlying)
newPosition.z() = tracer.mEndPos.z() + sGroundOffset; newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
isOnGround = true; isOnGround = true;
@ -292,7 +268,7 @@ namespace MWPhysics
btVector3 aabbMin, aabbMax; btVector3 aabbMin, aabbMax;
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
btVector3 center = (aabbMin + aabbMax) / 2.f; btVector3 center = (aabbMin + aabbMax) / 2.f;
inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0); inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
inertia.normalize(); inertia.normalize();
inertia *= 100; inertia *= 100;
} }
@ -302,16 +278,16 @@ namespace MWPhysics
} }
} }
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying) if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
else else
{ {
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
if (inertia.z() < 0) if (inertia.z() < 0)
inertia.z() *= slowFall; inertia.z() *= actor.mSlowFall;
if (slowFall < 1.f) { if (actor.mSlowFall < 1.f) {
inertia.x() *= slowFall; inertia.x() *= actor.mSlowFall;
inertia.y() *= slowFall; inertia.y() *= actor.mSlowFall;
} }
physicActor->setInertialForce(inertia); physicActor->setInertialForce(inertia);
} }
@ -319,6 +295,6 @@ namespace MWPhysics
physicActor->setOnSlope(isOnSlope); physicActor->setOnSlope(isOnSlope);
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
return newPosition; actor.mPosition = newPosition;
} }
} }

View file

@ -5,13 +5,18 @@
#include <osg/Vec3f> #include <osg/Vec3f>
#include "../mwworld/ptr.hpp"
class btCollisionWorld; class btCollisionWorld;
namespace MWWorld
{
class Ptr;
}
namespace MWPhysics namespace MWPhysics
{ {
class Actor; class Actor;
struct ActorFrameData;
struct WorldFrameData;
class MovementSolver class MovementSolver
{ {
@ -31,9 +36,7 @@ namespace MWPhysics
public: public:
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker);
}; };
} }

View file

@ -0,0 +1,607 @@
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <LinearMath/btThreads.h>
#include "components/debug/debuglog.hpp"
#include <components/misc/barrier.hpp>
#include "components/misc/convert.hpp"
#include "components/settings/settings.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "actor.hpp"
#include "movementsolver.hpp"
#include "mtphysics.hpp"
#include "object.hpp"
#include "physicssystem.hpp"
class btIParallelSumBody; // needed to compile with bullet < 2.88
namespace
{
/// @brief A scoped lock that is either shared or exclusive depending on configuration
template<class Mutex>
class MaybeSharedLock
{
public:
/// @param mutex a shared mutex
/// @param canBeSharedLock decide wether the lock will be shared or exclusive
MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock)
{
if (mCanBeSharedLock)
mMutex.lock_shared();
else
mMutex.lock();
}
~MaybeSharedLock()
{
if (mCanBeSharedLock)
mMutex.unlock_shared();
else
mMutex.unlock();
}
private:
Mutex& mMutex;
bool mCanBeSharedLock;
};
void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed)
{
const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround());
if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1)
actorData.mNeedLand = true;
else if (heightDiff < 0)
actorData.mFallHeight += heightDiff;
}
void handleJump(const MWWorld::Ptr &ptr)
{
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
}
// Decrease fatigue
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
{
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
}
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
}
void updateStandingCollision(MWPhysics::ActorFrameData& actorData, MWPhysics::CollisionMap& standingCollisions)
{
if (!actorData.mStandingOn.isEmpty())
standingCollisions[actorData.mPtr] = actorData.mStandingOn;
else
standingCollisions.erase(actorData.mPtr);
}
void updateMechanics(MWPhysics::ActorFrameData& actorData)
{
if (actorData.mDidJump)
handleJump(actorData.mPtr);
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
if (actorData.mNeedLand)
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
osg::Vec3f interpolateMovements(const MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = timeAccum / physicsDt;
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
}
struct WorldFrameData
{
WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm())
, mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection())
{}
bool mIsInStorm;
osg::Vec3f mStormDirection;
};
namespace Config
{
/* The purpose of these 2 classes is to make OpenMW works with Bullet compiled with either single or multithread support.
At runtime, Bullet resolve the call to btParallelFor() to:
- btITaskScheduler::parallelFor() if bullet is multithreaded
- btIParallelForBody::forLoop() if bullet is singlethreaded.
NOTE: From Bullet 2.88, there is a btDefaultTaskScheduler(), that returns NULL if multithreading is not supported.
It might be worth considering to simplify the API once OpenMW stops supporting 2.87.
*/
template<class ...>
using void_t = void;
/// @brief for Bullet <= 2.87
template <class T, class = void>
class MultiThreadedBulletImpl : public T
{
public:
MultiThreadedBulletImpl(): T("") {};
~MultiThreadedBulletImpl() override = default;
int getMaxNumThreads() const override { return 1; };
int getNumThreads() const override { return 1; };
void setNumThreads(int numThreads) override {};
/// @brief will be called by Bullet if threading is supported
void parallelFor(int iBegin, int iEnd, int batchsize, const btIParallelForBody& body) override {};
};
/// @brief for Bullet >= 2.88
template <class T>
class MultiThreadedBulletImpl<T, void_t<decltype(&T::parallelSum)>> : public T
{
public:
MultiThreadedBulletImpl(): T("") {};
~MultiThreadedBulletImpl() override = default;
int getMaxNumThreads() const override { return 1; };
int getNumThreads() const override { return 1; };
void setNumThreads(int numThreads) override {};
/// @brief will be called by Bullet if threading is supported
void parallelFor(int iBegin, int iEnd, int batchsize, const btIParallelForBody& body) override {};
btScalar parallelSum(int iBegin, int iEnd, int grainSize, const btIParallelSumBody& body) override { return {}; };
};
using MultiThreadedBullet = MultiThreadedBulletImpl<btITaskScheduler>;
class SingleThreadedBullet : public btIParallelForBody
{
public:
explicit SingleThreadedBullet(bool &threadingSupported): mThreadingSupported(threadingSupported) {};
/// @brief will be called by Bullet if threading is NOT supported
void forLoop(int iBegin, int iEnd) const override
{
mThreadingSupported = false;
}
private:
bool &mThreadingSupported;
};
/// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading
int computeNumThreads(bool& threadSafeBullet)
{
int wantedThread = Settings::Manager::getInt("async num threads", "Physics");
auto bulletScheduler = std::make_unique<MultiThreadedBullet>();
btSetTaskScheduler(bulletScheduler.get());
bool threadingSupported = true;
btParallelFor(0, 0, 0, SingleThreadedBullet(threadingSupported));
threadSafeBullet = threadingSupported;
if (!threadingSupported && wantedThread > 1)
{
Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used";
return 1;
}
return std::max(0, wantedThread);
}
}
}
namespace MWPhysics
{
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
: mPhysicsDt(physicsDt)
, mCollisionWorld(std::move(collisionWorld))
, mNumJobs(0)
, mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
, mDeferAabbUpdate(Settings::Manager::getBool("defer aabb update", "Physics"))
, mNewFrame(false)
, mAdvanceSimulation(false)
, mQuit(false)
, mNextJob(0)
, mNextLOS(0)
{
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
if (mNumThreads >= 1)
{
for (int i = 0; i < mNumThreads; ++i)
mThreads.emplace_back([&] { worker(); } );
}
else
{
mLOSCacheExpiry = -1;
mDeferAabbUpdate = false;
}
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{
updateAabbs();
});
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{
if (mRemainingSteps)
--mRemainingSteps;
mNextJob.store(0, std::memory_order_release);
updateActorsPositions();
});
mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{
udpateActorsAabbs();
mNewFrame = false;
if (mLOSCacheExpiry >= 0)
{
std::unique_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
mLOSCache.erase(
std::remove_if(mLOSCache.begin(), mLOSCache.end(),
[](const LOSRequest& req) { return req.mStale; }),
mLOSCache.end());
}
});
}
PhysicsTaskScheduler::~PhysicsTaskScheduler()
{
std::unique_lock<std::shared_timed_mutex> lock(mSimulationMutex);
mQuit = true;
mNumJobs = 0;
mRemainingSteps = 0;
lock.unlock();
mHasJob.notify_all();
for (auto& thread : mThreads)
thread.join();
}
const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, CollisionMap& standingCollisions, bool skipSimulation)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock<std::shared_timed_mutex> lock(mSimulationMutex);
// start by finishing previous background computation
if (mNumThreads != 0)
{
if (mAdvanceSimulation)
standingCollisions.clear();
for (auto& data : mActorsFrameData)
{
// Ignore actors that were deleted while the background thread was running
if (!data.mActor.lock())
continue;
updateMechanics(data);
if (mAdvanceSimulation)
updateStandingCollision(data, standingCollisions);
}
}
// init
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true;
mNumJobs = mActorsFrameData.size();
mNextLOS.store(0, std::memory_order_relaxed);
mNextJob.store(0, std::memory_order_release);
if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>();
// update each actor position based on latest data
for (auto& data : mActorsFrameData)
data.updatePosition();
// we are asked to skip the simulation (load a savegame for instance)
// just return the actors' reference position without applying the movements
if (skipSimulation)
{
standingCollisions.clear();
mMovementResults.clear();
for (const auto& m : mActorsFrameData)
mMovementResults[m.mPtr] = m.mPosition;
return mMovementResults;
}
if (mNumThreads == 0)
{
mMovementResults.clear();
syncComputation();
if (mAdvanceSimulation)
{
standingCollisions.clear();
for (auto& data : mActorsFrameData)
updateStandingCollision(data, standingCollisions);
}
return mMovementResults;
}
// Remove actors that were deleted while the background thread was running
for (auto& data : mActorsFrameData)
{
if (!data.mActor.lock())
mMovementResults.erase(data.mPtr);
}
std::swap(mMovementResults, mPreviousMovementResults);
// mMovementResults is shared between all workers instance
// pre-allocate all nodes so that we don't need synchronization
mMovementResults.clear();
for (const auto& m : mActorsFrameData)
mMovementResults[m.mPtr] = m.mPosition;
lock.unlock();
mHasJob.notify_all();
return mPreviousMovementResults;
}
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
{
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback);
}
void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const
{
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback);
}
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
mCollisionWorld->contactTest(colObj, resultCallback);
}
boost::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
{
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
// target the collision object's world origin, this should be the center of the collision object
btTransform rayTo;
rayTo.setIdentity();
rayTo.setOrigin(target->getWorldTransform().getOrigin());
btCollisionWorld::ClosestRayResultCallback cb(from.getOrigin(), rayTo.getOrigin());
mCollisionWorld->rayTestSingle(from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb);
if (!cb.hasHit())
// didn't hit the target. this could happen if point is already inside the collision box
return boost::none;
return {cb.m_hitPointWorld};
}
void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback)
{
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback);
}
void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max)
{
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max);
}
void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask)
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask;
}
void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask)
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask);
}
void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject)
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
mCollisionWorld->removeCollisionObject(collisionObject);
}
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
{
if (mDeferAabbUpdate)
{
std::unique_lock<std::mutex> lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
else
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
}
}
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
{
std::unique_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
auto actorPtr1 = actor1.lock();
auto actorPtr2 = actor2.lock();
if (!actorPtr1 || !actorPtr2)
return false;
auto req = LOSRequest(actor1, actor2);
auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req);
if (result == mLOSCache.end())
{
req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
if (mLOSCacheExpiry >= 0)
mLOSCache.push_back(req);
return req.mResult;
}
result->mAge = 0;
return result->mResult;
}
void PhysicsTaskScheduler::refreshLOSCache()
{
std::shared_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
int job = 0;
int numLOS = mLOSCache.size();
while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS)
{
auto& req = mLOSCache[job];
auto actorPtr1 = req.mActors[0].lock();
auto actorPtr2 = req.mActors[1].lock();
if (req.mAge++ > mLOSCacheExpiry || !actorPtr1 || !actorPtr2)
req.mStale = true;
else
req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
}
}
void PhysicsTaskScheduler::updateAabbs()
{
std::unique_lock<std::shared_timed_mutex> lock1(mCollisionWorldMutex, std::defer_lock);
std::unique_lock<std::mutex> lock2(mUpdateAabbMutex, std::defer_lock);
std::lock(lock1, lock2);
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
[this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
mUpdateAabb.clear();
}
void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr)
{
if (const auto p = ptr.lock())
{
if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
{
actor->commitPositionChange();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
}
else if (const auto object = std::dynamic_pointer_cast<Object>(p))
{
object->commitPositionChange();
mCollisionWorld->updateSingleAabb(object->getCollisionObject());
}
};
}
void PhysicsTaskScheduler::worker()
{
std::shared_lock<std::shared_timed_mutex> lock(mSimulationMutex);
while (!mQuit)
{
if (!mNewFrame)
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
if (mDeferAabbUpdate)
mPreStepBarrier->wait();
int job = 0;
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
{
MaybeSharedLock<std::shared_timed_mutex> lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
if(const auto actor = mActorsFrameData[job].mActor.lock())
{
if (mRemainingSteps)
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
else
{
auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
}
}
}
mPostStepBarrier->wait();
if (!mRemainingSteps)
{
if (mLOSCacheExpiry >= 0)
refreshLOSCache();
mPostSimBarrier->wait();
}
}
}
void PhysicsTaskScheduler::updateActorsPositions()
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
for (auto& actorData : mActorsFrameData)
{
if(const auto actor = actorData.mActor.lock())
{
if (actorData.mPosition == actor->getPosition())
actor->setPosition(actorData.mPosition, false); // update previous position to make sure interpolation is correct
else
{
actorData.mPositionChanged = true;
actor->setPosition(actorData.mPosition);
}
}
}
}
void PhysicsTaskScheduler::udpateActorsAabbs()
{
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
for (const auto& actorData : mActorsFrameData)
if (actorData.mPositionChanged)
{
if(const auto actor = actorData.mActor.lock())
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
}
}
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
{
btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level
btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9));
btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2);
resultCallback.m_collisionFilterGroup = 0xFF;
resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
MaybeSharedLock<std::shared_timed_mutex> lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
mCollisionWorld->rayTest(pos1, pos2, resultCallback);
return !resultCallback.hasHit();
}
void PhysicsTaskScheduler::syncComputation()
{
while (mRemainingSteps--)
{
for (auto& actorData : mActorsFrameData)
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
updateActorsPositions();
}
for (auto& actorData : mActorsFrameData)
{
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
updateMechanics(actorData);
}
udpateActorsAabbs();
}
}

View file

@ -0,0 +1,104 @@
#ifndef OPENMW_MWPHYSICS_MTPHYSICS_H
#define OPENMW_MWPHYSICS_MTPHYSICS_H
#include <atomic>
#include <condition_variable>
#include <thread>
#include <shared_mutex>
#include <boost/optional/optional.hpp>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include "physicssystem.hpp"
#include "ptrholder.hpp"
namespace Misc
{
class Barrier;
}
namespace MWPhysics
{
class PhysicsTaskScheduler
{
public:
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
~PhysicsTaskScheduler();
/// @brief move actors taking into account desired movements and collisions
/// @param numSteps how much simulation step to run
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, CollisionMap& standingCollisions, bool skip);
// Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
void convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const;
void contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback);
boost::optional<btVector3> getHitPoint(const btTransform& from, btCollisionObject* target);
void aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback);
void getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max);
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
private:
void syncComputation();
void worker();
void updateActorsPositions();
void udpateActorsAabbs();
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
void refreshLOSCache();
void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults;
PtrPositionList mPreviousMovementResults;
/*
Start of tes3mp change (major)
Turn mPhysicsDt into a non-const public variable so it can be set from elsewhere
*/
public:
float mPhysicsDt;
private:
/*
End of tes3mp change (major)
*/
float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::vector<LOSRequest> mLOSCache;
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
// TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing
std::unique_ptr<Misc::Barrier> mPreStepBarrier;
std::unique_ptr<Misc::Barrier> mPostStepBarrier;
std::unique_ptr<Misc::Barrier> mPostSimBarrier;
int mNumThreads;
int mNumJobs;
int mRemainingSteps;
int mLOSCacheExpiry;
bool mDeferAabbUpdate;
bool mNewFrame;
bool mAdvanceSimulation;
bool mThreadSafeBullet;
bool mQuit;
std::atomic<int> mNextJob;
std::atomic<int> mNextLOS;
std::vector<std::thread> mThreads;
mutable std::shared_timed_mutex mSimulationMutex;
mutable std::shared_timed_mutex mCollisionWorldMutex;
mutable std::shared_timed_mutex mLOSCacheMutex;
mutable std::mutex mUpdateAabbMutex;
std::condition_variable_any mHasJob;
};
}
#endif

View file

@ -1,4 +1,5 @@
#include "object.hpp" #include "object.hpp"
#include "mtphysics.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/nifosg/particle.hpp> #include <components/nifosg/particle.hpp>
@ -8,15 +9,15 @@
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
namespace MWPhysics namespace MWPhysics
{ {
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance) Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler)
: mShapeInstance(shapeInstance) : mShapeInstance(shapeInstance)
, mSolid(true) , mSolid(true)
, mTaskScheduler(scheduler)
{ {
mPtr = ptr; mPtr = ptr;
@ -29,6 +30,13 @@ namespace MWPhysics
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
const float* pos = ptr.getRefData().getPosition().pos; const float* pos = ptr.getRefData().getPosition().pos;
setOrigin(btVector3(pos[0], pos[1], pos[2])); setOrigin(btVector3(pos[0], pos[1], pos[2]));
commitPositionChange();
}
Object::~Object()
{
if (mCollisionObject)
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
} }
const Resource::BulletShapeInstance* Object::getShapeInstance() const const Resource::BulletShapeInstance* Object::getShapeInstance() const
@ -38,17 +46,38 @@ namespace MWPhysics
void Object::setScale(float scale) void Object::setScale(float scale)
{ {
mShapeInstance->setLocalScaling(btVector3(scale, scale, scale)); std::unique_lock<std::mutex> lock(mPositionMutex);
mScale = { scale,scale,scale };
mScaleUpdatePending = true;
} }
void Object::setRotation(const btQuaternion& quat) void Object::setRotation(const btQuaternion& quat)
{ {
mCollisionObject->getWorldTransform().setRotation(quat); std::unique_lock<std::mutex> lock(mPositionMutex);
mLocalTransform.setRotation(quat);
mTransformUpdatePending = true;
} }
void Object::setOrigin(const btVector3& vec) void Object::setOrigin(const btVector3& vec)
{ {
mCollisionObject->getWorldTransform().setOrigin(vec); std::unique_lock<std::mutex> lock(mPositionMutex);
mLocalTransform.setOrigin(vec);
mTransformUpdatePending = true;
}
void Object::commitPositionChange()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (mScaleUpdatePending)
{
mShapeInstance->setLocalScaling(mScale);
mScaleUpdatePending = false;
}
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
} }
btCollisionObject* Object::getCollisionObject() btCollisionObject* Object::getCollisionObject()
@ -61,6 +90,12 @@ namespace MWPhysics
return mCollisionObject.get(); return mCollisionObject.get();
} }
btTransform Object::getTransform() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return mLocalTransform;
}
bool Object::isSolid() const bool Object::isSolid() const
{ {
return mSolid; return mSolid;
@ -76,10 +111,10 @@ namespace MWPhysics
return !mShapeInstance->mAnimatedShapes.empty(); return !mShapeInstance->mAnimatedShapes.empty();
} }
void Object::animateCollisionShapes(btCollisionWorld* collisionWorld) bool Object::animateCollisionShapes()
{ {
if (mShapeInstance->mAnimatedShapes.empty()) if (mShapeInstance->mAnimatedShapes.empty())
return; return false;
assert (mShapeInstance->getCollisionShape()->isCompound()); assert (mShapeInstance->getCollisionShape()->isCompound());
@ -100,7 +135,7 @@ namespace MWPhysics
// Remove nonexistent nodes from animated shapes map and early out // Remove nonexistent nodes from animated shapes map and early out
mShapeInstance->mAnimatedShapes.erase(recIndex); mShapeInstance->mAnimatedShapes.erase(recIndex);
return; return false;
} }
osg::NodePath nodePath = visitor.mFoundPath; osg::NodePath nodePath = visitor.mFoundPath;
nodePath.erase(nodePath.begin()); nodePath.erase(nodePath.begin());
@ -122,7 +157,6 @@ namespace MWPhysics
if (!(transform == compound->getChildTransform(shapeIndex))) if (!(transform == compound->getChildTransform(shapeIndex)))
compound->updateChildTransform(shapeIndex, transform); compound->updateChildTransform(shapeIndex, transform);
} }
return true;
collisionWorld->updateSingleAabb(mCollisionObject.get());
} }
} }

View file

@ -3,10 +3,12 @@
#include "ptrholder.hpp" #include "ptrholder.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Node> #include <osg/Node>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex>
namespace Resource namespace Resource
{ {
@ -14,34 +16,46 @@ namespace Resource
} }
class btCollisionObject; class btCollisionObject;
class btCollisionWorld;
class btQuaternion; class btQuaternion;
class btVector3; class btVector3;
namespace MWPhysics namespace MWPhysics
{ {
class Object : public PtrHolder class PhysicsTaskScheduler;
class Object final : public PtrHolder
{ {
public: public:
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance); Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler);
~Object() override;
const Resource::BulletShapeInstance* getShapeInstance() const; const Resource::BulletShapeInstance* getShapeInstance() const;
void setScale(float scale); void setScale(float scale);
void setRotation(const btQuaternion& quat); void setRotation(const btQuaternion& quat);
void setOrigin(const btVector3& vec); void setOrigin(const btVector3& vec);
void commitPositionChange();
btCollisionObject* getCollisionObject(); btCollisionObject* getCollisionObject();
const btCollisionObject* getCollisionObject() const; const btCollisionObject* getCollisionObject() const;
btTransform getTransform() const;
/// Return solid flag. Not used by the object itself, true by default. /// Return solid flag. Not used by the object itself, true by default.
bool isSolid() const; bool isSolid() const;
void setSolid(bool solid); void setSolid(bool solid);
bool isAnimated() const; bool isAnimated() const;
void animateCollisionShapes(btCollisionWorld* collisionWorld); /// @brief update object shape
/// @return true if shape changed
bool animateCollisionShapes();
private: private:
std::unique_ptr<btCollisionObject> mCollisionObject; std::unique_ptr<btCollisionObject> mCollisionObject;
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance; osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
std::map<int, osg::NodePath> mRecIndexToNodePath; std::map<int, osg::NodePath> mRecIndexToNodePath;
bool mSolid; bool mSolid;
btVector3 mScale;
btTransform mLocalTransform;
bool mScaleUpdatePending;
bool mTransformUpdatePending;
mutable std::mutex mPositionMutex;
PhysicsTaskScheduler* mTaskScheduler;
}; };
} }

View file

@ -31,6 +31,7 @@
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
@ -50,6 +51,7 @@
#include "contacttestresultcallback.hpp" #include "contacttestresultcallback.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "movementsolver.hpp" #include "movementsolver.hpp"
#include "mtphysics.hpp"
namespace MWPhysics namespace MWPhysics
{ {
@ -65,11 +67,11 @@ namespace MWPhysics
{ {
mResourceSystem->addResourceManager(mShapeManager.get()); mResourceSystem->addResourceManager(mShapeManager.get());
mCollisionConfiguration = new btDefaultCollisionConfiguration(); mCollisionConfiguration = std::make_unique<btDefaultCollisionConfiguration>();
mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
mBroadphase = new btDbvtBroadphase(); mBroadphase = std::make_unique<btDbvtBroadphase>();
mCollisionWorld = new btCollisionWorld(mDispatcher, mBroadphase, mCollisionConfiguration); mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
@ -86,36 +88,26 @@ namespace MWPhysics
Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS)."; Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS).";
} }
} }
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
} }
PhysicsSystem::~PhysicsSystem() PhysicsSystem::~PhysicsSystem()
{ {
mResourceSystem->removeResourceManager(mShapeManager.get()); mResourceSystem->removeResourceManager(mShapeManager.get());
if (mWaterCollisionObject.get()) if (mWaterCollisionObject)
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
for (auto& heightField : mHeightFields) for (auto& heightField : mHeightFields)
{ {
mCollisionWorld->removeCollisionObject(heightField.second->getCollisionObject()); mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject());
delete heightField.second; delete heightField.second;
} }
for (auto& object : mObjects) mObjects.clear();
{ mActors.clear();
mCollisionWorld->removeCollisionObject(object.second->getCollisionObject());
delete object.second;
}
for (auto& actor : mActors)
{
delete actor.second;
}
delete mCollisionWorld;
delete mCollisionConfiguration;
delete mDispatcher;
delete mBroadphase;
} }
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -132,13 +124,13 @@ namespace MWPhysics
{ {
mDebugDrawEnabled = !mDebugDrawEnabled; mDebugDrawEnabled = !mDebugDrawEnabled;
if (mDebugDrawEnabled && !mDebugDrawer.get()) if (mDebugDrawEnabled && !mDebugDrawer)
{ {
mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld)); mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld.get()));
mCollisionWorld->setDebugDrawer(mDebugDrawer.get()); mCollisionWorld->setDebugDrawer(mDebugDrawer.get());
mDebugDrawer->setDebugMode(mDebugDrawEnabled); mDebugDrawer->setDebugMode(mDebugDrawEnabled);
} }
else if (mDebugDrawer.get()) else if (mDebugDrawer)
mDebugDrawer->setDebugMode(mDebugDrawEnabled); mDebugDrawer->setDebugMode(mDebugDrawEnabled);
return mDebugDrawEnabled; return mDebugDrawEnabled;
} }
@ -182,6 +174,8 @@ namespace MWPhysics
if (physFramerate > 0 && physFramerate < 100) if (physFramerate > 0 && physFramerate < 100)
{ {
mPhysicsDt = 1.f / physFramerate; mPhysicsDt = 1.f / physFramerate;
mTaskScheduler->mPhysicsDt = mPhysicsDt;
std::cerr << "Warning: physics framerate was overridden (a new value is " << physFramerate << ")." << std::endl; std::cerr << "Warning: physics framerate was overridden (a new value is " << physFramerate << ")." << std::endl;
} }
else else
@ -197,7 +191,7 @@ namespace MWPhysics
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin, const osg::Vec3f &origin,
const osg::Quat &orient, const osg::Quat &orient,
float queryDistance, std::vector<MWWorld::Ptr> targets) float queryDistance, std::vector<MWWorld::Ptr>& targets)
{ {
// First of all, try to hit where you aim to // First of all, try to hit where you aim to
int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
@ -243,7 +237,7 @@ namespace MWPhysics
DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin)); DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin));
resultCallback.m_collisionFilterGroup = CollisionType_Actor; resultCallback.m_collisionFilterGroup = CollisionType_Actor;
resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
mCollisionWorld->contactTest(&object, resultCallback); mTaskScheduler->contactTest(&object, resultCallback);
if (resultCallback.mObject) if (resultCallback.mObject)
{ {
@ -267,25 +261,22 @@ namespace MWPhysics
rayFrom.setIdentity(); rayFrom.setIdentity();
rayFrom.setOrigin(Misc::Convert::toBullet(point)); rayFrom.setOrigin(Misc::Convert::toBullet(point));
// target the collision object's world origin, this should be the center of the collision object auto hitpoint = mTaskScheduler->getHitPoint(rayFrom, targetCollisionObj);
btTransform rayTo; if (hitpoint)
rayTo.setIdentity(); return (point - Misc::Convert::toOsg(hitpoint.get())).length();
rayTo.setOrigin(targetCollisionObj->getWorldTransform().getOrigin());
btCollisionWorld::ClosestRayResultCallback cb(rayFrom.getOrigin(), rayTo.getOrigin()); // didn't hit the target. this could happen if point is already inside the collision box
return 0.f;
btCollisionWorld::rayTestSingle(rayFrom, rayTo, targetCollisionObj, targetCollisionObj->getCollisionShape(), targetCollisionObj->getWorldTransform(), cb);
if (!cb.hasHit())
{
// didn't hit the target. this could happen if point is already inside the collision box
return 0.f;
}
else
return (point - Misc::Convert::toOsg(cb.m_hitPointWorld)).length();
} }
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
{ {
if (from == to)
{
RayCastingResult result;
result.mHit = false;
return result;
}
btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btFrom = Misc::Convert::toBullet(from);
btVector3 btTo = Misc::Convert::toBullet(to); btVector3 btTo = Misc::Convert::toBullet(to);
@ -319,7 +310,7 @@ namespace MWPhysics
resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask; resultCallback.m_collisionFilterMask = mask;
mCollisionWorld->rayTest(btFrom, btTo, resultCallback); mTaskScheduler->rayTest(btFrom, btTo, resultCallback);
RayCastingResult result; RayCastingResult result;
result.mHit = resultCallback.hasHit(); result.mHit = resultCallback.hasHit();
@ -345,7 +336,7 @@ namespace MWPhysics
btTransform from_ (btrot, Misc::Convert::toBullet(from)); btTransform from_ (btrot, Misc::Convert::toBullet(from));
btTransform to_ (btrot, Misc::Convert::toBullet(to)); btTransform to_ (btrot, Misc::Convert::toBullet(to));
mCollisionWorld->convexSweepTest(&shape, from_, to_, callback); mTaskScheduler->convexSweepTest(&shape, from_, to_, callback);
RayCastingResult result; RayCastingResult result;
result.mHit = callback.hasHit(); result.mHit = callback.hasHit();
@ -359,18 +350,15 @@ namespace MWPhysics
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
{ {
const Actor* physactor1 = getActor(actor1); const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr<Actor>
const Actor* physactor2 = getActor(actor2); {
const auto found = mActors.find(ptr);
if (found != mActors.end())
return { found->second };
return {};
};
if (!physactor1 || !physactor2) return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2));
return false;
osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.9)); // eye level
osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.9));
RayCastingResult result = castRay(pos1, pos2, MWWorld::ConstPtr(), std::vector<MWWorld::Ptr>(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door);
return !result.mHit;
} }
bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor)
@ -389,7 +377,7 @@ namespace MWPhysics
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
ActorTracer tracer; ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld); tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
return (tracer.mFraction >= 1.0f); return (tracer.mFraction >= 1.0f);
} }
@ -424,7 +412,7 @@ namespace MWPhysics
const Object * physobject = getObject(object); const Object * physobject = getObject(object);
if (!physobject) return osg::BoundingBox(); if (!physobject) return osg::BoundingBox();
btVector3 min, max; btVector3 min, max;
physobject->getCollisionObject()->getCollisionShape()->getAabb(physobject->getCollisionObject()->getWorldTransform(), min, max); mTaskScheduler->getAabb(physobject->getCollisionObject(), min, max);
return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max)); return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max));
} }
@ -450,7 +438,7 @@ namespace MWPhysics
ContactTestResultCallback resultCallback (me); ContactTestResultCallback resultCallback (me);
resultCallback.m_collisionFilterGroup = collisionGroup; resultCallback.m_collisionFilterGroup = collisionGroup;
resultCallback.m_collisionFilterMask = collisionMask; resultCallback.m_collisionFilterMask = collisionMask;
mCollisionWorld->contactTest(me, resultCallback); mTaskScheduler->contactTest(me, resultCallback);
return resultCallback.mResult; return resultCallback.mResult;
} }
@ -460,7 +448,7 @@ namespace MWPhysics
if (found == mActors.end()) if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3(); return ptr.getRefData().getPosition().asVec3();
else else
return MovementSolver::traceDown(ptr, position, found->second, mCollisionWorld, maxHeight); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
} }
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
@ -468,7 +456,7 @@ namespace MWPhysics
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject); HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject);
mHeightFields[std::make_pair(x,y)] = heightfield; mHeightFields[std::make_pair(x,y)] = heightfield;
mCollisionWorld->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,
CollisionType_Actor|CollisionType_Projectile); CollisionType_Actor|CollisionType_Projectile);
} }
@ -477,7 +465,7 @@ namespace MWPhysics
HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y));
if(heightfield != mHeightFields.end()) if(heightfield != mHeightFields.end())
{ {
mCollisionWorld->removeCollisionObject(heightfield->second->getCollisionObject()); mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject());
delete heightfield->second; delete heightfield->second;
mHeightFields.erase(heightfield); mHeightFields.erase(heightfield);
} }
@ -497,13 +485,13 @@ namespace MWPhysics
if (!shapeInstance || !shapeInstance->getCollisionShape()) if (!shapeInstance || !shapeInstance->getCollisionShape())
return; return;
Object *obj = new Object(ptr, shapeInstance); auto obj = std::make_shared<Object>(ptr, shapeInstance, mTaskScheduler.get());
mObjects.emplace(ptr, obj); mObjects.emplace(ptr, obj);
if (obj->isAnimated()) if (obj->isAnimated())
mAnimatedObjects.insert(obj); mAnimatedObjects.insert(obj.get());
mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType, mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType,
CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
} }
@ -512,21 +500,17 @@ namespace MWPhysics
ObjectMap::iterator found = mObjects.find(ptr); ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end()) if (found != mObjects.end())
{ {
mCollisionWorld->removeCollisionObject(found->second->getCollisionObject());
if (mUnrefQueue.get()) if (mUnrefQueue.get())
mUnrefQueue->push(found->second->getShapeInstance()); mUnrefQueue->push(found->second->getShapeInstance());
mAnimatedObjects.erase(found->second); mAnimatedObjects.erase(found->second.get());
delete found->second;
mObjects.erase(found); mObjects.erase(found);
} }
ActorMap::iterator foundActor = mActors.find(ptr); ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end()) if (foundActor != mActors.end())
{ {
delete foundActor->second;
mActors.erase(foundActor); mActors.erase(foundActor);
} }
} }
@ -552,19 +536,19 @@ namespace MWPhysics
ObjectMap::iterator found = mObjects.find(old); ObjectMap::iterator found = mObjects.find(old);
if (found != mObjects.end()) if (found != mObjects.end())
{ {
Object* obj = found->second; auto obj = found->second;
obj->updatePtr(updated); obj->updatePtr(updated);
mObjects.erase(found); mObjects.erase(found);
mObjects.emplace(updated, obj); mObjects.emplace(updated, std::move(obj));
} }
ActorMap::iterator foundActor = mActors.find(old); ActorMap::iterator foundActor = mActors.find(old);
if (foundActor != mActors.end()) if (foundActor != mActors.end())
{ {
Actor* actor = foundActor->second; auto actor = foundActor->second;
actor->updatePtr(updated); actor->updatePtr(updated);
mActors.erase(foundActor); mActors.erase(foundActor);
mActors.emplace(updated, actor); mActors.emplace(updated, std::move(actor));
} }
updateCollisionMapPtr(mStandingCollisions, old, updated); updateCollisionMapPtr(mStandingCollisions, old, updated);
@ -574,7 +558,7 @@ namespace MWPhysics
{ {
ActorMap::iterator found = mActors.find(ptr); ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end()) if (found != mActors.end())
return found->second; return found->second.get();
return nullptr; return nullptr;
} }
@ -582,7 +566,7 @@ namespace MWPhysics
{ {
ActorMap::const_iterator found = mActors.find(ptr); ActorMap::const_iterator found = mActors.find(ptr);
if (found != mActors.end()) if (found != mActors.end())
return found->second; return found->second.get();
return nullptr; return nullptr;
} }
@ -590,7 +574,7 @@ namespace MWPhysics
{ {
ObjectMap::const_iterator found = mObjects.find(ptr); ObjectMap::const_iterator found = mObjects.find(ptr);
if (found != mObjects.end()) if (found != mObjects.end())
return found->second; return found->second.get();
return nullptr; return nullptr;
} }
@ -601,14 +585,14 @@ namespace MWPhysics
{ {
float scale = ptr.getCellRef().getScale(); float scale = ptr.getCellRef().getScale();
found->second->setScale(scale); found->second->setScale(scale);
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(found->second);
return; return;
} }
ActorMap::iterator foundActor = mActors.find(ptr); ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end()) if (foundActor != mActors.end())
{ {
foundActor->second->updateScale(); foundActor->second->updateScale();
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(foundActor->second);
return; return;
} }
} }
@ -619,7 +603,7 @@ namespace MWPhysics
if (found != mObjects.end()) if (found != mObjects.end())
{ {
found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(found->second);
return; return;
} }
ActorMap::iterator foundActor = mActors.find(ptr); ActorMap::iterator foundActor = mActors.find(ptr);
@ -628,7 +612,7 @@ namespace MWPhysics
if (!foundActor->second->isRotationallyInvariant()) if (!foundActor->second->isRotationallyInvariant())
{ {
foundActor->second->updateRotation(); foundActor->second->updateRotation();
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(foundActor->second);
} }
return; return;
} }
@ -640,14 +624,14 @@ namespace MWPhysics
if (found != mObjects.end()) if (found != mObjects.end())
{ {
found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(found->second);
return; return;
} }
ActorMap::iterator foundActor = mActors.find(ptr); ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end()) if (foundActor != mActors.end())
{ {
foundActor->second->updatePosition(); foundActor->second->updatePosition();
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(foundActor->second);
return; return;
} }
} }
@ -655,11 +639,9 @@ 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<const Resource::BulletShape> shape = mShapeManager->getShape(mesh); osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
if (!shape)
return;
// Try to get shape from basic model as fallback for creatures // Try to get shape from basic model as fallback for creatures
if (!ptr.getClass().isNpc() && shape->mCollisionBoxHalfExtents.length2() == 0) if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
{ {
const std::string fallbackModel = ptr.getClass().getModel(ptr); const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh) if (fallbackModel != mesh)
@ -668,8 +650,11 @@ namespace MWPhysics
} }
} }
Actor* actor = new Actor(ptr, shape, mCollisionWorld); if (!shape)
mActors.emplace(ptr, actor); return;
auto actor = std::make_shared<Actor>(ptr, shape, mTaskScheduler.get());
mActors.emplace(ptr, std::move(actor));
} }
bool PhysicsSystem::toggleCollisionMode() bool PhysicsSystem::toggleCollisionMode()
@ -707,99 +692,76 @@ namespace MWPhysics
mStandingCollisions.clear(); mStandingCollisions.clear();
} }
const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation)
{ {
mMovementResults.clear();
mTimeAccum += dt; mTimeAccum += dt;
const int maxAllowedSteps = 20; const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / (mPhysicsDt); int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps); numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt; mTimeAccum -= numSteps * mPhysicsDt;
if (numSteps) return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(), mStandingCollisions, skipSimulation);
{ }
// Collision events should be available on every frame
mStandingCollisions.clear();
}
const MWWorld::Ptr player = MWMechanics::getPlayer(); std::vector<ActorFrameData> PhysicsSystem::prepareFrameData()
{
std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size());
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
for(auto& movementItem : mMovementQueue) for (const auto& m : mMovementQueue)
{ {
ActorMap::iterator foundActor = mActors.find(movementItem.first); const auto& character = m.first;
const auto& movement = m.second;
const auto foundActor = mActors.find(character);
if (foundActor == mActors.end()) // actor was already removed from the scene if (foundActor == mActors.end()) // actor was already removed from the scene
{
mStandingCollisions.erase(character);
continue; continue;
Actor* physicActor = foundActor->second; }
auto physicActor = foundActor->second;
float waterlevel = -std::numeric_limits<float>::max(); float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = movementItem.first.getCell(); const MWWorld::CellStore *cell = character.getCell();
if(cell->getCell()->hasWater()) if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel(); waterlevel = cell->getWaterLevel();
const MWMechanics::MagicEffects& effects = movementItem.first.getClass().getCreatureStats(movementItem.first).getMagicEffects(); const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false; bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{ {
if (!world->isUnderwater(movementItem.first.getCell(), osg::Vec3f(movementItem.first.getRefData().getPosition().asVec3()))) if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true; waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(movementItem.first, waterlevel)) else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{ {
const osg::Vec3f actorPosition = physicActor->getPosition(); moveToWaterSurface = true;
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
waterCollision = true; waterCollision = true;
} }
} }
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
// Slow fall reduces fall speed by a factor of (effect magnitude / 200) // 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)); const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
bool flying = world->isFlying(movementItem.first); actorsFrameData.emplace_back(std::move(physicActor), character, mStandingCollisions[character], moveToWaterSurface, movement, slowFall, waterlevel);
bool swimming = world->isSwimming(movementItem.first);
bool wasOnGround = physicActor->getOnGround();
osg::Vec3f position = physicActor->getPosition();
float oldHeight = position.z();
bool positionChanged = false;
for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, movementItem.second, mPhysicsDt,
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
if (position != physicActor->getPosition())
positionChanged = true;
physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct
}
if (positionChanged)
mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject());
float interpolationFactor = mTimeAccum / mPhysicsDt;
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
float heightDiff = position.z() - oldHeight;
MWMechanics::CreatureStats& stats = movementItem.first.getClass().getCreatureStats(movementItem.first);
bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
if (isStillOnGround || flying || swimming || slowFall < 1)
stats.land(movementItem.first == player && (flying || swimming));
else if (heightDiff < 0)
stats.addToFallHeight(-heightDiff);
mMovementResults.emplace_back(movementItem.first, interpolated);
} }
mMovementQueue.clear(); mMovementQueue.clear();
return actorsFrameData;
return mMovementResults;
} }
void PhysicsSystem::stepSimulation(float dt) void PhysicsSystem::stepSimulation()
{ {
for (Object* animatedObject : mAnimatedObjects) for (Object* animatedObject : mAnimatedObjects)
animatedObject->animateCollisionShapes(mCollisionWorld); if (animatedObject->animateCollisionShapes())
{
auto obj = mObjects.find(animatedObject->getPtr());
assert(obj != mObjects.end());
mTaskScheduler->updateSingleAabb(obj->second);
}
#ifndef BT_NO_PROFILE #ifndef BT_NO_PROFILE
CProfileManager::Reset(); CProfileManager::Reset();
@ -811,12 +773,13 @@ namespace MWPhysics
{ {
ObjectMap::iterator found = mObjects.find(object); ObjectMap::iterator found = mObjects.find(object);
if (found != mObjects.end()) if (found != mObjects.end())
found->second->animateCollisionShapes(mCollisionWorld); if (found->second->animateCollisionShapes())
mTaskScheduler->updateSingleAabb(found->second);
} }
void PhysicsSystem::debugDraw() void PhysicsSystem::debugDraw()
{ {
if (mDebugDrawer.get()) if (mDebugDrawer)
mDebugDrawer->step(); mDebugDrawer->step();
} }
@ -881,9 +844,9 @@ namespace MWPhysics
void PhysicsSystem::updateWater() void PhysicsSystem::updateWater()
{ {
if (mWaterCollisionObject.get()) if (mWaterCollisionObject)
{ {
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
} }
if (!mWaterEnabled) if (!mWaterEnabled)
@ -895,7 +858,7 @@ namespace MWPhysics
mWaterCollisionObject.reset(new btCollisionObject()); mWaterCollisionObject.reset(new btCollisionObject());
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
CollisionType_Actor); CollisionType_Actor);
} }
@ -911,7 +874,7 @@ namespace MWPhysics
const int mask = MWPhysics::CollisionType_Actor; const int mask = MWPhysics::CollisionType_Actor;
const int group = 0xff; const int group = 0xff;
HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group);
mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
return callback.getResult(); return callback.getResult();
} }
@ -921,4 +884,61 @@ namespace MWPhysics
stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); stats.setAttribute(frameNumber, "Physics Objects", mObjects.size());
stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size());
} }
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mPositionChanged(false), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
mPtr = actor->getPtr();
mFlying = world->isFlying(character);
mSwimming = world->isSwimming(character);
mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0;
mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead();
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition()
{
mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface)
{
mPosition.z() = mWaterlevel;
mActorRaw->setPosition(mPosition);
}
mOldHeight = mPosition.z();
mRefpos = mPtr.getRefData().getPosition();
}
WorldFrameData::WorldFrameData()
: mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm())
, mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection())
{}
LOSRequest::LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2)
: mResult(false), mStale(false), mAge(0)
{
// we use raw actor pointer pair to uniquely identify request
// sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and getLOS(B, A)
auto* raw1 = a1.lock().get();
auto* raw2 = a2.lock().get();
assert(raw1 != raw2);
if (raw1 < raw2)
{
mActors = {a1, a2};
mRawActors = {raw1, raw2};
}
else
{
mActors = {a2, a1};
mRawActors = {raw2, raw1};
}
}
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept
{
return lhs.mRawActors == rhs.mRawActors;
}
} }

View file

@ -1,6 +1,7 @@
#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#include <array>
#include <memory> #include <memory>
#include <map> #include <map>
#include <set> #include <set>
@ -47,11 +48,57 @@ class btCollisionShape;
namespace MWPhysics namespace MWPhysics
{ {
typedef std::vector<std::pair<MWWorld::Ptr,osg::Vec3f> > PtrVelocityList; using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
using CollisionMap = std::map<MWWorld::Ptr, MWWorld::Ptr>;
class HeightField; class HeightField;
class Object; class Object;
class Actor; class Actor;
class PhysicsTaskScheduler;
struct LOSRequest
{
LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2);
std::array<std::weak_ptr<Actor>, 2> mActors;
std::array<const Actor*, 2> mRawActors;
bool mResult;
bool mStale;
int mAge;
};
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept;
struct ActorFrameData
{
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition();
std::weak_ptr<Actor> mActor;
Actor* mActorRaw;
MWWorld::Ptr mPtr;
MWWorld::Ptr mStandingOn;
bool mFlying;
bool mSwimming;
bool mPositionChanged;
bool mWasOnGround;
bool mWantJump;
bool mDidJump;
bool mIsDead;
bool mNeedLand;
bool mMoveToWaterSurface;
float mWaterlevel;
float mSlowFall;
float mOldHeight;
float mFallHeight;
osg::Vec3f mMovement;
osg::Vec3f mPosition;
ESM::Position mRefpos;
};
struct WorldFrameData
{
WorldFrameData();
bool mIsInStorm;
osg::Vec3f mStormDirection;
};
class PhysicsSystem : public RayCastingInterface class PhysicsSystem : public RayCastingInterface
{ {
@ -93,7 +140,7 @@ namespace MWPhysics
bool toggleCollisionMode(); bool toggleCollisionMode();
void stepSimulation(float dt); void stepSimulation();
void debugDraw(); void debugDraw();
std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with
@ -102,7 +149,7 @@ namespace MWPhysics
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor, std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin, const osg::Vec3f &origin,
const osg::Quat &orientation, const osg::Quat &orientation,
float queryDistance, std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>()); float queryDistance, std::vector<MWWorld::Ptr>& targets);
/// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the
@ -146,7 +193,7 @@ namespace MWPhysics
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list. /// Apply all queued movements, then clear the list.
const PtrVelocityList& applyQueuedMovement(float dt); const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation);
/// Clear the queued movements list without applying. /// Clear the queued movements list without applying.
void clearQueuedMovement(); void clearQueuedMovement();
@ -200,39 +247,41 @@ namespace MWPhysics
void updateWater(); void updateWater();
std::vector<ActorFrameData> prepareFrameData();
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue; osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
btBroadphaseInterface* mBroadphase; std::unique_ptr<btBroadphaseInterface> mBroadphase;
btDefaultCollisionConfiguration* mCollisionConfiguration; std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
btCollisionDispatcher* mDispatcher; std::unique_ptr<btCollisionDispatcher> mDispatcher;
btCollisionWorld* mCollisionWorld; std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager; std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
typedef std::map<MWWorld::ConstPtr, Object*> ObjectMap; using ObjectMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Object>>;
ObjectMap mObjects; ObjectMap mObjects;
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
typedef std::map<MWWorld::ConstPtr, Actor*> ActorMap; using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
ActorMap mActors; ActorMap mActors;
typedef std::map<std::pair<int, int>, HeightField*> HeightFieldMap; using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields; HeightFieldMap mHeightFields;
bool mDebugDrawEnabled; bool mDebugDrawEnabled;
// Tracks standing collisions happening during a single frame. <actor handle, collided handle> // Tracks standing collisions happening during a single frame. <actor handle, collided handle>
// This will detect standing on an object, but won't detect running e.g. against a wall. // This will detect standing on an object, but won't detect running e.g. against a wall.
typedef std::map<MWWorld::Ptr, MWWorld::Ptr> CollisionMap;
CollisionMap mStandingCollisions; CollisionMap mStandingCollisions;
// replaces all occurrences of 'old' in the map by 'updated', no matter if it's a key or value // replaces all occurrences of 'old' in the map by 'updated', no matter if it's a key or value
void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated); void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated);
using PtrVelocityList = std::vector<std::pair<MWWorld::Ptr, osg::Vec3f>>;
PtrVelocityList mMovementQueue; PtrVelocityList mMovementQueue;
PtrVelocityList mMovementResults;
float mTimeAccum; float mTimeAccum;

View file

@ -252,11 +252,15 @@ osg::Group *CreatureWeaponAnimation::getArrowBone()
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType; int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh
osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone);
mWeapon->getNode()->accept(findVisitor); if (bone == nullptr)
{
return findVisitor.mFoundNode; SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
mWeapon->getNode()->accept(findVisitor);
bone = findVisitor.mFoundNode;
}
return bone;
} }
osg::Node *CreatureWeaponAnimation::getWeaponNode() osg::Node *CreatureWeaponAnimation::getWeaponNode()

View file

@ -1078,10 +1078,15 @@ osg::Group* NpcAnimation::getArrowBone()
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType; int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh
part->getNode()->accept(findVisitor); osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone);
if (bone == nullptr)
return findVisitor.mFoundNode; {
SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
part->getNode()->accept(findVisitor);
bone = findVisitor.mFoundNode;
}
return bone;
} }
osg::Node* NpcAnimation::getWeaponNode() osg::Node* NpcAnimation::getWeaponNode()

View file

@ -29,6 +29,7 @@ namespace MWWorld
MWWorld::Ptr target = getTarget(); MWWorld::Ptr target = getTarget();
MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); MWWorld::ContainerStore& store = target.getClass().getContainerStore (target);
store.resolve();
MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor);
std::map<std::string, int> takenMap; std::map<std::string, int> takenMap;
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)

View file

@ -40,7 +40,7 @@ namespace MWWorld
{ {
// Find any NPCs that are following the actor and teleport them with him // Find any NPCs that are following the actor and teleport them with him
std::set<MWWorld::Ptr> followers; std::set<MWWorld::Ptr> followers;
getFollowersToTeleport(actor, followers); getFollowers(actor, followers, true);
for (std::set<MWWorld::Ptr>::iterator it = followers.begin(); it != followers.end(); ++it) for (std::set<MWWorld::Ptr>::iterator it = followers.begin(); it != followers.end(); ++it)
teleport(*it); teleport(*it);
@ -82,7 +82,9 @@ namespace MWWorld
MWWorld::CellStore *newCellStore; MWWorld::CellStore *newCellStore;
mwmp::CellController *cellController = mwmp::Main::get().getCellController(); mwmp::CellController *cellController = mwmp::Main::get().getCellController();
if (mCellName.empty()) if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(world->getPlayerPtr()))
actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat();
else if (mCellName.empty())
{ {
int cellX; int cellX;
int cellY; int cellY;
@ -150,7 +152,7 @@ namespace MWWorld
} }
} }
void ActionTeleport::getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) { void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out, bool includeHostiles) {
std::set<MWWorld::Ptr> followers; std::set<MWWorld::Ptr> followers;
MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers);
@ -159,11 +161,17 @@ namespace MWWorld
MWWorld::Ptr follower = *it; MWWorld::Ptr follower = *it;
std::string script = follower.getClass().getScript(follower); std::string script = follower.getClass().getScript(follower);
if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor))
continue;
if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1)
continue; continue;
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800) if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800)
out.insert(follower); continue;
out.emplace(follower);
} }
} }
} }

View file

@ -28,8 +28,9 @@ namespace MWWorld
/// @param teleportFollowers Whether to teleport any following actors of the target actor as well. /// @param teleportFollowers Whether to teleport any following actors of the target actor as well.
ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers);
/// Outputs every actor follower who is in teleport range and wasn't ordered to not enter interiors /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output,
static void getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out); /// e.g. so that the teleport action can calm them.
static void getFollowers(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out, bool includeHostiles = false);
}; };
} }

View file

@ -1247,7 +1247,8 @@ namespace MWWorld
for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
{ {
Ptr ptr = getCurrentPtr(&*it); Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0) if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0
&& ptr.getClass().getContainerStore(ptr).isResolved())
{ {
ptr.getClass().getContainerStore(ptr).rechargeItems(duration); ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
} }

View file

@ -364,8 +364,6 @@ namespace MWWorld
virtual void respawn (const MWWorld::Ptr& ptr) const {} virtual void respawn (const MWWorld::Ptr& ptr) const {}
virtual void restock (const MWWorld::Ptr& ptr) const {}
/// Returns sound id /// Returns sound id
virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; virtual std::string getSound(const MWWorld::ConstPtr& ptr) const;

View file

@ -12,6 +12,7 @@
#include "../mwmp/Main.hpp" #include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp" #include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp" #include "../mwmp/LocalPlayer.hpp"
#include <components/openmw-mp/TimedLog.hpp>
/* /*
End of tes3mp addition End of tes3mp addition
*/ */
@ -35,6 +36,21 @@
namespace namespace
{ {
void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell)
{
auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts();
for(const MWWorld::Ptr& ptr : store)
{
const std::string& script = ptr.getClass().getScript(ptr);
if(!script.empty())
{
MWWorld::Ptr item = ptr;
item.mCell = cell;
scripts.add(script, item);
}
}
}
template<typename T> template<typename T>
float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList) float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList)
{ {
@ -56,6 +72,7 @@ namespace
MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id, MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id,
MWWorld::ContainerStore *store) MWWorld::ContainerStore *store)
{ {
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id); std::string id2 = Misc::StringUtils::lowerCase (id);
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin()); for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
@ -73,6 +90,18 @@ namespace
} }
} }
MWWorld::ResolutionListener::~ResolutionListener()
{
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : mStore)
ptr.getRefData().setCount(0);
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell);
mStore.mResolved = false;
}
}
template<typename T> template<typename T>
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection, MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection,
const ESM::ObjectState& state) const ESM::ObjectState& state)
@ -131,7 +160,11 @@ MWWorld::ContainerStore::ContainerStore()
: mListener(nullptr) : mListener(nullptr)
, mRechargingItemsUpToDate(false) , mRechargingItemsUpToDate(false)
, mCachedWeight (0) , mCachedWeight (0)
, mWeightUpToDate (false) {} , mWeightUpToDate (false)
, mModified(false)
, mResolved(false)
, mSeed()
, mPtr() {}
MWWorld::ContainerStore::~ContainerStore() {} MWWorld::ContainerStore::~ContainerStore() {}
@ -165,22 +198,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
return ContainerStoreIterator (this); return ContainerStoreIterator (this);
} }
int MWWorld::ContainerStore::count(const std::string &id) int MWWorld::ContainerStore::count(const std::string &id) const
{ {
int total=0; int total=0;
for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) for (const auto& iter : *this)
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
total += iter->getRefData().getCount(); total += iter.getRefData().getCount();
return total;
}
int MWWorld::ContainerStore::restockCount(const std::string &id)
{
int total=0;
for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
if (iter->getCellRef().getSoul().empty())
total += iter->getRefData().getCount();
return total; return total;
} }
@ -197,9 +220,10 @@ void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* l
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count)
{ {
resolve();
if (ptr.getRefData().getCount() <= count) if (ptr.getRefData().getCount() <= count)
return end(); return end();
MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count); MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count));
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -230,6 +254,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr,
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item)
{ {
resolve();
MWWorld::ContainerStoreIterator retval = end(); MWWorld::ContainerStoreIterator retval = end();
for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
{ {
@ -247,7 +272,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::
{ {
if (stacks(*iter, item)) if (stacks(*iter, item))
{ {
iter->getRefData().setCount(iter->getRefData().getCount() + item.getRefData().getCount()); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false)));
item.getRefData().setCount(0); item.getRefData().setCount(0);
retval = iter; retval = iter;
break; break;
@ -391,8 +416,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
return it; return it;
} }
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count, bool markModified)
{ {
if(markModified)
resolve();
int type = getType(ptr); int type = getType(ptr);
const MWWorld::ESMStore &esmStore = const MWWorld::ESMStore &esmStore =
@ -408,7 +435,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr,
{ {
if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId))
{ {
iter->getRefData().setCount(iter->getRefData().getCount() + realCount); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount));
flagAsModified(); flagAsModified();
return iter; return iter;
} }
@ -424,7 +451,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr,
if (stacks(*iter, ptr)) if (stacks(*iter, ptr))
{ {
// stack // stack
iter->getRefData().setCount( iter->getRefData().getCount() + count ); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
flagAsModified(); flagAsModified();
return iter; return iter;
@ -502,6 +529,7 @@ void MWWorld::ContainerStore::updateRechargingItems()
int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor)
{ {
resolve();
int toRemove = count; int toRemove = count;
for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter)
@ -528,6 +556,7 @@ bool MWWorld::ContainerStore::hasVisibleItems() const
int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor)
{ {
assert(this == item.getContainerStore()); assert(this == item.getContainerStore());
resolve();
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -557,7 +586,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
} }
else else
{ {
itemRef.setCount(itemRef.getCount() - toRemove); itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove));
toRemove = 0; toRemove = 0;
} }
@ -580,20 +609,33 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
return count - toRemove; return count - toRemove;
} }
void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner) void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed)
{ {
for (std::vector<ESM::ContItem>::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); for (const ESM::ContItem& iter : items.mList)
++iter)
{ {
std::string id = Misc::StringUtils::lowerCase(iter->mItem); std::string id = Misc::StringUtils::lowerCase(iter.mItem);
addInitialItem(id, owner, iter->mCount); addInitialItem(id, owner, iter.mCount, &seed);
} }
flagAsModified(); flagAsModified();
mResolved = true;
} }
void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed)
int count, bool topLevel, const std::string& levItem) {
mSeed = seed;
for (const ESM::ContItem& iter : items.mList)
{
std::string id = Misc::StringUtils::lowerCase(iter.mItem);
addInitialItem(id, owner, iter.mCount, nullptr);
}
flagAsModified();
mResolved = false;
}
void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count,
Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem)
{ {
if (count == 0) return; //Don't restock with nothing. if (count == 0) return; //Don't restock with nothing.
try try
@ -601,13 +643,13 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) if (ref.getPtr().getClass().getScript(ref.getPtr()).empty())
{ {
addInitialItemImp(ref.getPtr(), owner, count, topLevel, levItem); addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel, levItem);
} }
else else
{ {
// Adding just one item per time to make sure there isn't a stack of scripted items // Adding just one item per time to make sure there isn't a stack of scripted items
for (int i = 0; i < abs(count); i++) for (int i = 0; i < std::abs(count); i++)
addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, topLevel, levItem); addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel, levItem);
} }
} }
catch (const std::exception& e) catch (const std::exception& e)
@ -616,137 +658,43 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
} }
} }
void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count,
int count, bool topLevel, const std::string& levItem) Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem)
{ {
if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) if (ptr.getTypeName()==typeid (ESM::ItemLevList).name())
{ {
if(!seed)
return;
const ESM::ItemLevList* levItemList = ptr.get<ESM::ItemLevList>()->mBase; const ESM::ItemLevList* levItemList = ptr.get<ESM::ItemLevList>()->mBase;
if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each)
{ {
for (int i=0; i<std::abs(count); ++i) for (int i=0; i<std::abs(count); ++i)
addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, true, levItemList->mId); addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, seed, true, levItemList->mId);
return; return;
} }
else else
{ {
std::string itemId = MWMechanics::getLevelledItem(ptr.get<ESM::ItemLevList>()->mBase, false); std::string itemId = MWMechanics::getLevelledItem(ptr.get<ESM::ItemLevList>()->mBase, false, *seed);
if (itemId.empty()) if (itemId.empty())
return; return;
addInitialItem(itemId, owner, count, false, levItemList->mId); addInitialItem(itemId, owner, count, seed, false, levItemList->mId);
} }
} }
else else
{ {
// A negative count indicates restocking items
// For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks
if (!levItem.empty() && count < 0)
{
//If there is no item in map, insert it
std::map<std::pair<std::string, std::string>, int>::iterator itemInMap =
mLevelledItemMap.insert(std::make_pair(std::make_pair(ptr.getCellRef().getRefId(), levItem), 0)).first;
//Update spawned count
itemInMap->second += std::abs(count);
}
count = std::abs(count);
ptr.getCellRef().setOwner(owner); ptr.getCellRef().setOwner(owner);
addImp (ptr, count); addImp (ptr, count, false);
} }
} }
void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner)
{
//allowedForReplace - Holds information about how many items from the list were not sold;
// Hence, tells us how many items we don't need to restock.
//allowedForReplace[list] <- How many items we should generate(how many of these were sold)
std::map<std::string, int> allowedForReplace;
//Check which lists need restocking:
for (std::map<std::pair<std::string, std::string>, int>::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end();)
{
int spawnedCount = it->second; //How many items should be in shop originally
int itemCount = restockCount(it->first.first); //How many items are there in shop now
//If something was not sold
if(itemCount >= spawnedCount)
{
const std::string& parent = it->first.second;
// Security check for old saves:
//If item is imported from old save(doesn't have an parent) and wasn't sold
if(parent == "")
{
//Remove it, from shop,
remove(it->first.first, itemCount, ptr);//ptr is the NPC
//And remove it from map, so that when we restock, the new item will have proper parent.
mLevelledItemMap.erase(it++);
continue;
}
//Create the entry if it does not exist yet
std::map<std::string, int>::iterator listInMap = allowedForReplace.insert(
std::make_pair(it->first.second, 0)).first;
//And signal that we don't need to restock item from this list
listInMap->second += std::abs(itemCount);
}
//If every of the item was sold
else if (itemCount == 0)
{
mLevelledItemMap.erase(it++);
continue;
}
//If some was sold, but some remain
else
{
//Create entry if it does not exist yet
std::map<std::string, int>::iterator listInMap = allowedForReplace.insert(
std::make_pair(it->first.second, 0)).first;
//And signal that we don't need to restock all items from this list
listInMap->second += std::abs(itemCount);
//And update itemCount so we don't mistake it next time.
it->second = itemCount;
}
++it;
}
//Restock:
//For every item that NPC could have
for (std::vector<ESM::ContItem>::const_iterator it = items.mList.begin(); it != items.mList.end(); ++it)
{
//If he shouldn't have it restocked, don't restock it.
if (it->mCount >= 0)
continue;
std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem);
//If it's levelled list, restock if there's need to do so.
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::ItemLevList>().search(it->mItem))
{
std::map<std::string, int>::iterator listInMap = allowedForReplace.find(itemOrList);
int restockNum = std::abs(it->mCount);
//If we know we must restock less, take it into account
if(listInMap != allowedForReplace.end())
restockNum -= std::min(restockNum, listInMap->second);
//restock
addInitialItem(itemOrList, owner, -restockNum, true);
}
else
{
//Restocking static item - just restock to the max count
int currentCount = restockCount(itemOrList);
if (currentCount < std::abs(it->mCount))
addInitialItem(itemOrList, owner, -(std::abs(it->mCount) - currentCount), true);
}
}
flagAsModified();
}
void MWWorld::ContainerStore::clear() void MWWorld::ContainerStore::clear()
{ {
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
iter->getRefData().setCount (0); iter->getRefData().setCount (0);
flagAsModified(); flagAsModified();
mModified = true;
} }
void MWWorld::ContainerStore::flagAsModified() void MWWorld::ContainerStore::flagAsModified()
@ -755,6 +703,59 @@ void MWWorld::ContainerStore::flagAsModified()
mRechargingItemsUpToDate = false; mRechargingItemsUpToDate = false;
} }
bool MWWorld::ContainerStore::isResolved() const
{
return mResolved;
}
/*
Start of tes3mp addiition
Make it possible to set the container's resolved state from elsewhere, to avoid unnecessary
refills before overriding its contents
*/
void MWWorld::ContainerStore::setResolved(bool state)
{
mResolved = state;
}
/*
End of tes3mp addition
*/
void MWWorld::ContainerStore::resolve()
{
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
addScripts(*this, mPtr.mCell);
}
mModified = true;
}
MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily()
{
if(mModified)
return {};
std::shared_ptr<ResolutionListener> listener = mResolutionListener.lock();
if(!listener)
{
listener = std::make_shared<ResolutionListener>(*this);
mResolutionListener = listener;
}
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
addScripts(*this, mPtr.mCell);
}
return {listener};
}
float MWWorld::ContainerStore::getWeight() const float MWWorld::ContainerStore::getWeight() const
{ {
if (!mWeightUpToDate) if (!mWeightUpToDate)
@ -851,6 +852,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
{ {
resolve();
{ {
Ptr ptr = searchId (potions, id, this); Ptr ptr = searchId (potions, id, this);
if (!ptr.isEmpty()) if (!ptr.isEmpty())
@ -926,6 +928,22 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
return Ptr(); return Ptr();
} }
int MWWorld::ContainerStore::addItems(int count1, int count2)
{
int sum = std::abs(count1) + std::abs(count2);
if(count1 < 0 || count2 < 0)
return -sum;
return sum;
}
int MWWorld::ContainerStore::subtractItems(int count1, int count2)
{
int sum = std::abs(count1) - std::abs(count2);
if(count1 < 0 || count2 < 0)
return -sum;
return sum;
}
void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
{ {
state.mItems.clear(); state.mItems.clear();
@ -943,13 +961,13 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
storeStates (repairs, state, index); storeStates (repairs, state, index);
storeStates (weapons, state, index, true); storeStates (weapons, state, index, true);
storeStates (lights, state, index, true); storeStates (lights, state, index, true);
state.mLevelledItemMap = mLevelledItemMap;
} }
void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
{ {
clear(); clear();
mModified = true;
mResolved = true;
int index = 0; int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator for (std::vector<ESM::ObjectState>::const_iterator
@ -983,9 +1001,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
break; break;
} }
} }
mLevelledItemMap = inventory.mLevelledItemMap;
} }
template<class PtrType> template<class PtrType>

View file

@ -3,6 +3,7 @@
#include <iterator> #include <iterator>
#include <map> #include <map>
#include <memory>
#include <utility> #include <utility>
#include <components/esm/loadalch.hpp> #include <components/esm/loadalch.hpp>
@ -18,6 +19,8 @@
#include <components/esm/loadrepa.hpp> #include <components/esm/loadrepa.hpp>
#include <components/esm/loadweap.hpp> #include <components/esm/loadweap.hpp>
#include <components/misc/rng.hpp>
#include "ptr.hpp" #include "ptr.hpp"
#include "cellreflist.hpp" #include "cellreflist.hpp"
@ -27,6 +30,11 @@ namespace ESM
struct InventoryState; struct InventoryState;
} }
namespace MWClass
{
class Container;
}
namespace MWWorld namespace MWWorld
{ {
class ContainerStore; class ContainerStore;
@ -37,6 +45,21 @@ namespace MWWorld
typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator; typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator;
typedef ContainerStoreIteratorBase<ConstPtr> ConstContainerStoreIterator; typedef ContainerStoreIteratorBase<ConstPtr> ConstContainerStoreIterator;
class ResolutionListener
{
ContainerStore& mStore;
public:
ResolutionListener(ContainerStore& store) : mStore(store) {}
~ResolutionListener();
};
class ResolutionHandle
{
std::shared_ptr<ResolutionListener> mListener;
public:
ResolutionHandle(std::shared_ptr<ResolutionListener> listener) : mListener(listener) {}
ResolutionHandle() {}
};
class ContainerStoreListener class ContainerStoreListener
{ {
@ -93,15 +116,18 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Repair> repairs; MWWorld::CellRefList<ESM::Repair> repairs;
MWWorld::CellRefList<ESM::Weapon> weapons; MWWorld::CellRefList<ESM::Weapon> weapons;
std::map<std::pair<std::string, std::string>, int> mLevelledItemMap;
///< Stores result of levelled item spawns. <(refId, spawningGroup), count>
/// This is used to restock levelled items(s) if the old item was sold.
mutable float mCachedWeight; mutable float mCachedWeight;
mutable bool mWeightUpToDate; mutable bool mWeightUpToDate;
ContainerStoreIterator addImp (const Ptr& ptr, int count);
void addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); bool mModified;
void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); bool mResolved;
unsigned int mSeed;
MWWorld::Ptr mPtr;
std::weak_ptr<ResolutionListener> mResolutionListener;
ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true);
void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true, const std::string& levItem = "");
void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true, const std::string& levItem = "");
template<typename T> template<typename T>
ContainerStoreIterator getState (CellRefList<T>& collection, ContainerStoreIterator getState (CellRefList<T>& collection,
@ -175,31 +201,31 @@ namespace MWWorld
/// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted.
/// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item.
int count (const std::string& id); int count (const std::string& id) const;
///< @return How many items with refID \a id are in this container? ///< @return How many items with refID \a id are in this container?
int restockCount (const std::string& id);
///< Item count with restock adjustments (such as ignoring filled soul gems).
/// @return How many items with refID \a id are in this container?
ContainerStoreListener* getContListener() const; ContainerStoreListener* getContListener() const;
void setContListener(ContainerStoreListener* listener); void setContListener(ContainerStoreListener* listener);
protected: protected:
ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count);
///< Add the item to this container (do not try to stack it onto existing items) ///< Add the item to this container (do not try to stack it onto existing items)
virtual void flagAsModified(); virtual void flagAsModified();
/// + and - operations that can deal with negative stacks
/// Note that negativity is infectious
static int addItems(int count1, int count2);
static int subtractItems(int count1, int count2);
public: public:
virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
///< @return true if the two specified objects can stack with each other ///< @return true if the two specified objects can stack with each other
void fill (const ESM::InventoryList& items, const std::string& owner); void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed = Misc::Rng::getSeed());
///< Insert items into *this. ///< Insert items into *this.
void restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner); void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed);
///< Insert items into *this, excluding leveled items
virtual void clear(); virtual void clear();
///< Empty container. ///< Empty container.
@ -220,8 +246,26 @@ namespace MWWorld
virtual void readState (const ESM::InventoryState& state); virtual void readState (const ESM::InventoryState& state);
bool isResolved() const;
/*
Start of tes3mp addiition
Make it possible to set the container's resolved state from elsewhere, to avoid unnecessary
refills before overriding its contents
*/
void setResolved(bool state);
/*
End of tes3mp addition
*/
void resolve();
ResolutionHandle resolveTemporarily();
friend class ContainerStoreIteratorBase<Ptr>; friend class ContainerStoreIteratorBase<Ptr>;
friend class ContainerStoreIteratorBase<ConstPtr>; friend class ContainerStoreIteratorBase<ConstPtr>;
friend class ResolutionListener;
friend class MWClass::Container;
}; };

View file

@ -99,8 +99,9 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt
// unstack if required // unstack if required
if (!allowedSlots.second && iter->getRefData().getCount() > 1) if (!allowedSlots.second && iter->getRefData().getCount() > 1)
{ {
MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1); int count = iter->getRefData().getCount(false);
iter->getRefData().setCount(iter->getRefData().getCount()-1); MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1);
iter->getRefData().setCount(subtractItems(count, 1));
mSlots[slot] = newIter; mSlots[slot] = newIter;
} }
else else
@ -903,8 +904,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con
{ {
if (stacks(*iter, item) && !isEquipped(*iter)) if (stacks(*iter, item) && !isEquipped(*iter))
{ {
iter->getRefData().setCount(iter->getRefData().getCount() + count); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
item.getRefData().setCount(item.getRefData().getCount() - count); item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count));
return iter; return iter;
} }
} }

View file

@ -45,7 +45,7 @@ namespace
// Ignore containers without generated content // Ignore containers without generated content
if (containerPtr.getTypeName() == typeid(ESM::Container).name() && if (containerPtr.getTypeName() == typeid(ESM::Container).name() &&
containerPtr.getRefData().getCustomData() == nullptr) containerPtr.getRefData().getCustomData() == nullptr)
return false; return true;
MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr);
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)

View file

@ -242,7 +242,8 @@ namespace MWWorld
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player);
if (playerStats.isParalyzed() || playerStats.getKnockedDown() || playerStats.isDead()) bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
if ((!godmode && playerStats.isParalyzed()) || playerStats.getKnockedDown() || playerStats.isDead())
return; return;
MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject(); MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject();

View file

@ -146,8 +146,10 @@ namespace MWWorld
return mBaseNode; return mBaseNode;
} }
int RefData::getCount() const int RefData::getCount(bool absolute) const
{ {
if(absolute)
return std::abs(mCount);
return mCount; return mCount;
} }

View file

@ -86,7 +86,7 @@ namespace MWWorld
/// Set base node (can be a null pointer). /// Set base node (can be a null pointer).
void setBaseNode (SceneUtil::PositionAttitudeTransform* base); void setBaseNode (SceneUtil::PositionAttitudeTransform* base);
int getCount() const; int getCount(bool absolute = true) const;
void setLocals (const ESM::Script& script); void setLocals (const ESM::Script& script);

View file

@ -163,7 +163,7 @@ namespace
? btVector3(distanceFromDoor, 0, 0) ? btVector3(distanceFromDoor, 0, 0)
: btVector3(0, distanceFromDoor, 0); : btVector3(0, distanceFromDoor, 0);
const auto& transform = object->getCollisionObject()->getWorldTransform(); const auto transform = object->getTransform();
const btTransform closedDoorTransform( const btTransform closedDoorTransform(
Misc::Convert::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), Misc::Convert::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())),
transform.getOrigin() transform.getOrigin()
@ -198,7 +198,7 @@ namespace
*object->getShapeInstance()->getCollisionShape(), *object->getShapeInstance()->getCollisionShape(),
object->getShapeInstance()->getAvoidCollisionShape() object->getShapeInstance()->getAvoidCollisionShape()
}, },
object->getCollisionObject()->getWorldTransform() object->getTransform()
); );
} }
} }

View file

@ -167,7 +167,7 @@ namespace MWWorld
const std::string& resourcePath, const std::string& userDataPath) const std::string& resourcePath, const std::string& userDataPath)
: mResourceSystem(resourceSystem), mLocalScripts (mStore), : mResourceSystem(resourceSystem), mLocalScripts (mStore),
mCells (mStore, mEsm), mSky (true), mCells (mStore, mEsm), mSky (true),
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
mUserDataPath(userDataPath), mShouldUpdateNavigator(false), mUserDataPath(userDataPath), mShouldUpdateNavigator(false),
mActivationDistanceOverride (activationDistanceOverride), mActivationDistanceOverride (activationDistanceOverride),
mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true),
@ -1050,6 +1050,7 @@ namespace MWWorld
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{ {
mPhysics->clearQueuedMovement(); mPhysics->clearQueuedMovement();
mDiscardMovements = true;
if (changeEvent && mCurrentWorldSpace != cellName) if (changeEvent && mCurrentWorldSpace != cellName)
{ {
@ -1069,6 +1070,7 @@ namespace MWWorld
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{ {
mPhysics->clearQueuedMovement(); mPhysics->clearQueuedMovement();
mDiscardMovements = true;
if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace) if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
{ {
@ -1688,24 +1690,23 @@ namespace MWWorld
void World::doPhysics(float duration) void World::doPhysics(float duration)
{ {
mPhysics->stepSimulation(duration); mPhysics->stepSimulation();
processDoors(duration); processDoors(duration);
mProjectileManager->update(duration); mProjectileManager->update(duration);
const MWPhysics::PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements);
MWPhysics::PtrVelocityList::const_iterator player(results.end()); mDiscardMovements = false;
for(MWPhysics::PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter)
for(const auto& result : results)
{ {
if(iter->first == getPlayerPtr()) // Handle player last, in case a cell transition occurs
{ if(result.first != getPlayerPtr())
// Handle player last, in case a cell transition occurs moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false);
player = iter;
continue;
}
moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false);
} }
if(player != results.end())
const auto player = results.find(getPlayerPtr());
if (player != results.end())
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
} }
@ -1733,7 +1734,7 @@ namespace MWWorld
*object->getShapeInstance()->getCollisionShape(), *object->getShapeInstance()->getCollisionShape(),
object->getShapeInstance()->getAvoidCollisionShape() object->getShapeInstance()->getAvoidCollisionShape()
}; };
return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getCollisionObject()->getWorldTransform()); return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getTransform());
} }
const MWPhysics::RayCastingInterface* World::getRayCasting() const const MWPhysics::RayCastingInterface* World::getRayCasting() const
@ -2062,10 +2063,13 @@ namespace MWWorld
else else
mRendering->getCamera()->setSneakOffset(0.f); mRendering->getCamera()->setSneakOffset(0.f);
int blind = static_cast<int>(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude()); int blind = 0;
auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects();
if (!mGodMode)
blind = static_cast<int>(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude());
MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind)));
int nightEye = static_cast<int>(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude()); int nightEye = static_cast<int>(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude());
mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f)));
} }
@ -4350,7 +4354,7 @@ namespace MWWorld
btVector3 aabbMax; btVector3 aabbMax;
object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
const auto toLocal = object->getCollisionObject()->getWorldTransform().inverse(); const auto toLocal = object->getTransform().inverse();
const auto localFrom = toLocal(Misc::Convert::toBullet(position)); const auto localFrom = toLocal(Misc::Convert::toBullet(position));
const auto localTo = toLocal(Misc::Convert::toBullet(destination)); const auto localTo = toLocal(Misc::Convert::toBullet(destination));

View file

@ -102,6 +102,7 @@ namespace MWWorld
bool mSky; bool mSky;
bool mGodMode; bool mGodMode;
bool mScriptsEnabled; bool mScriptsEnabled;
bool mDiscardMovements;
std::vector<std::string> mContentFiles; std::vector<std::string> mContentFiles;
std::string mUserDataPath; std::string mUserDataPath;

View file

@ -97,7 +97,6 @@ TEST(EsmFixedString, struct_size)
ASSERT_EQ(4, sizeof(ESM::NAME)); ASSERT_EQ(4, sizeof(ESM::NAME));
ASSERT_EQ(32, sizeof(ESM::NAME32)); ASSERT_EQ(32, sizeof(ESM::NAME32));
ASSERT_EQ(64, sizeof(ESM::NAME64)); ASSERT_EQ(64, sizeof(ESM::NAME64));
ASSERT_EQ(256, sizeof(ESM::NAME256));
} }
TEST(EsmFixedString, is_pod) TEST(EsmFixedString, is_pod)
@ -105,5 +104,4 @@ TEST(EsmFixedString, is_pod)
ASSERT_TRUE(std::is_pod<ESM::NAME>::value); ASSERT_TRUE(std::is_pod<ESM::NAME>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME32>::value); ASSERT_TRUE(std::is_pod<ESM::NAME32>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME64>::value); ASSERT_TRUE(std::is_pod<ESM::NAME64>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME256>::value);
} }

View file

@ -244,6 +244,7 @@ namespace
void init(Nif::Named& value) void init(Nif::Named& value)
{ {
value.extra = Nif::ExtraPtr(nullptr); value.extra = Nif::ExtraPtr(nullptr);
value.extralist = Nif::ExtraList();
value.controller = Nif::ControllerPtr(nullptr); value.controller = Nif::ControllerPtr(nullptr);
} }

View file

@ -92,7 +92,7 @@ namespace
"\n" "\n"
"void bar() { foo() }\n" "void bar() { foo() }\n"
"\n" "\n"
"#line 2 0\n" "#line 1 0\n"
"\n" "\n"
"void main() { bar() }\n"; "void main() { bar() }\n";
EXPECT_EQ(shader->getShaderSource(), expected); EXPECT_EQ(shader->getShaderSource(), expected);

View file

@ -10,8 +10,8 @@ namespace Compiler
class SourceException : public std::exception class SourceException : public std::exception
{ {
public: public:
virtual const char *what() const throw() { return "Compile error";} const char *what() const noexcept override { return "Compile error";}
///< Return error message ///< Return error message
}; };
@ -20,18 +20,18 @@ namespace Compiler
class FileException : public SourceException class FileException : public SourceException
{ {
public: public:
virtual const char *what() const throw() { return "Can't read file"; } const char *what() const noexcept final { return "Can't read file"; }
///< Return error message ///< Return error message
}; };
/// \brief Exception: EOF condition encountered /// \brief Exception: EOF condition encountered
class EOFException : public SourceException class EOFException : public SourceException
{ {
public: public:
virtual const char *what() const throw() { return "End of file"; } const char *what() const noexcept final { return "End of file"; }
///< Return error message ///< Return error message
}; };
} }

View file

@ -38,8 +38,14 @@ namespace DetourNavigator
return stream << "failed"; return stream << "failed";
case UpdateNavMeshStatus::lost: case UpdateNavMeshStatus::lost:
return stream << "lost"; return stream << "lost";
case UpdateNavMeshStatus::cached:
return stream << "cached";
case UpdateNavMeshStatus::unchanged:
return stream << "unchanged";
case UpdateNavMeshStatus::restored:
return stream << "restored";
} }
return stream << "unknown"; return stream << "unknown(" << static_cast<unsigned>(value) << ")";
} }
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
@ -126,7 +132,7 @@ namespace DetourNavigator
mNavMeshTilesCache.reportStats(frameNumber, stats); mNavMeshTilesCache.reportStats(frameNumber, stats);
} }
void AsyncNavMeshUpdater::process() throw() void AsyncNavMeshUpdater::process() noexcept
{ {
Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id(); Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id();
while (!mShouldStop) while (!mShouldStop)

View file

@ -113,7 +113,7 @@ namespace DetourNavigator
std::map<std::thread::id, Queue> mThreadsQueues; std::map<std::thread::id, Queue> mThreadsQueues;
std::vector<std::thread> mThreads; std::vector<std::thread> mThreads;
void process() throw(); void process() noexcept;
bool processJob(const Job& job); bool processJob(const Job& job);

View file

@ -36,7 +36,7 @@ namespace DetourNavigator
dtPolyRef resultRef = 0; dtPolyRef resultRef = 0;
osg::Vec3f resultPosition; osg::Vec3f resultPosition;
navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter, navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter,
&Misc::Rng::rollProbability, &resultRef, resultPosition.ptr()); []() { return Misc::Rng::rollProbability(); }, &resultRef, resultPosition.ptr());
if (resultRef == 0) if (resultRef == 0)
return boost::optional<osg::Vec3f>(); return boost::optional<osg::Vec3f>();

View file

@ -559,6 +559,7 @@ namespace DetourNavigator
} }
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
bool cached = static_cast<bool>(cachedNavMeshData);
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
@ -584,6 +585,7 @@ namespace DetourNavigator
{ {
cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh,
offMeshConnections); offMeshConnections);
cached = static_cast<bool>(cachedNavMeshData);
} }
if (!cachedNavMeshData) if (!cachedNavMeshData)
@ -593,6 +595,8 @@ namespace DetourNavigator
} }
} }
return navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData)); const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData));
return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult();
} }
} }

View file

@ -22,6 +22,9 @@ namespace DetourNavigator
replaced = removed | added, replaced = removed | added,
failed = 1 << 2, failed = 1 << 2,
lost = removed | failed, lost = removed | failed,
cached = 1 << 3,
unchanged = replaced | cached,
restored = added | cached,
}; };
inline bool isSuccess(UpdateNavMeshStatus value) inline bool isSuccess(UpdateNavMeshStatus value)
@ -34,6 +37,9 @@ namespace DetourNavigator
public: public:
UpdateNavMeshStatusBuilder() = default; UpdateNavMeshStatusBuilder() = default;
explicit UpdateNavMeshStatusBuilder(UpdateNavMeshStatus value)
: mResult(value) {}
UpdateNavMeshStatusBuilder removed(bool value) UpdateNavMeshStatusBuilder removed(bool value)
{ {
if (value) if (value)
@ -61,6 +67,15 @@ namespace DetourNavigator
return *this; return *this;
} }
UpdateNavMeshStatusBuilder cached(bool value)
{
if (value)
set(UpdateNavMeshStatus::cached);
else
unset(UpdateNavMeshStatus::cached);
return *this;
}
UpdateNavMeshStatus getResult() const UpdateNavMeshStatus getResult() const
{ {
return mResult; return mResult;
@ -143,7 +158,7 @@ namespace DetourNavigator
UpdateNavMeshStatus removeTile(const TilePosition& position) UpdateNavMeshStatus removeTile(const TilePosition& position)
{ {
const auto removed = dtStatusSucceed(removeTileImpl(position)); const auto removed = removeTileImpl(position);
if (removed) if (removed)
removeUsedTile(position); removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).getResult(); return UpdateNavMeshStatusBuilder().removed(removed).getResult();
@ -181,13 +196,15 @@ namespace DetourNavigator
return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result); return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result);
} }
dtStatus removeTileImpl(const TilePosition& position) bool removeTileImpl(const TilePosition& position)
{ {
const int layer = 0; const int layer = 0;
const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer); const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer);
if (tileRef == 0)
return false;
unsigned char** const data = nullptr; unsigned char** const data = nullptr;
int* const dataSize = nullptr; int* const dataSize = nullptr;
return mImpl->removeTile(tileRef, data, dataSize); return dtStatusSucceed(mImpl->removeTile(tileRef, data, dataSize));
} }
}; };

View file

@ -9,42 +9,32 @@ namespace DetourNavigator
{ {
namespace namespace
{ {
inline std::string makeNavMeshKey(const RecastMesh& recastMesh, inline std::vector<unsigned char> makeNavMeshKey(const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& offMeshConnections) const std::vector<OffMeshConnection>& offMeshConnections)
{ {
std::string result; const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int);
result.reserve( const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float);
recastMesh.getIndices().size() * sizeof(int) const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType);
+ recastMesh.getVertices().size() * sizeof(float) const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water);
+ recastMesh.getAreaTypes().size() * sizeof(AreaType) const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection);
+ recastMesh.getWater().size() * sizeof(RecastMesh::Water)
+ offMeshConnections.size() * sizeof(OffMeshConnection) std::vector<unsigned char> result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize);
); unsigned char* dst = result.data();
std::copy(
reinterpret_cast<const char*>(recastMesh.getIndices().data()), std::memcpy(dst, recastMesh.getIndices().data(), indicesSize);
reinterpret_cast<const char*>(recastMesh.getIndices().data() + recastMesh.getIndices().size()), dst += indicesSize;
std::back_inserter(result)
); std::memcpy(dst, recastMesh.getVertices().data(), verticesSize);
std::copy( dst += verticesSize;
reinterpret_cast<const char*>(recastMesh.getVertices().data()),
reinterpret_cast<const char*>(recastMesh.getVertices().data() + recastMesh.getVertices().size()), std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize);
std::back_inserter(result) dst += areaTypesSize;
);
std::copy( std::memcpy(dst, recastMesh.getWater().data(), waterSize);
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data()), dst += waterSize;
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()),
std::back_inserter(result) std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize);
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getWater().data()),
reinterpret_cast<const char*>(recastMesh.getWater().data() + recastMesh.getWater().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(offMeshConnections.data()),
reinterpret_cast<const char*>(offMeshConnections.data() + offMeshConnections.size()),
std::back_inserter(result)
);
return result; return result;
} }
} }
@ -189,8 +179,8 @@ namespace DetourNavigator
{ {
struct CompareBytes struct CompareBytes
{ {
const char* mRhsIt; const unsigned char* mRhsIt;
const char* mRhsEnd; const unsigned char* const mRhsEnd;
template <class T> template <class T>
int operator ()(const std::vector<T>& lhs) int operator ()(const std::vector<T>& lhs)
@ -225,7 +215,7 @@ namespace DetourNavigator
}; };
} }
int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const int NavMeshTilesCache::RecastMeshKeyView::compare(const std::vector<unsigned char>& other) const
{ {
CompareBytes compareBytes {other.data(), other.data() + other.size()}; CompareBytes compareBytes {other.data(), other.data() + other.size()};

View file

@ -11,6 +11,8 @@
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <cassert> #include <cassert>
#include <cstring>
#include <vector>
namespace osg namespace osg
{ {
@ -33,10 +35,10 @@ namespace DetourNavigator
std::atomic<std::int64_t> mUseCount; std::atomic<std::int64_t> mUseCount;
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
TilePosition mChangedTile; TilePosition mChangedTile;
std::string mNavMeshKey; std::vector<unsigned char> mNavMeshKey;
NavMeshData mNavMeshData; NavMeshData mNavMeshData;
Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey) Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::vector<unsigned char>&& navMeshKey)
: mUseCount(0) : mUseCount(0)
, mAgentHalfExtents(agentHalfExtents) , mAgentHalfExtents(agentHalfExtents)
, mChangedTile(changedTile) , mChangedTile(changedTile)
@ -120,19 +122,32 @@ namespace DetourNavigator
virtual ~KeyView() = default; virtual ~KeyView() = default;
KeyView(const std::string& value) KeyView(const std::vector<unsigned char>& value)
: mValue(&value) {} : mValue(&value) {}
const std::string& getValue() const const std::vector<unsigned char>& getValue() const
{ {
assert(mValue); assert(mValue);
return *mValue; return *mValue;
} }
virtual int compare(const std::string& other) const virtual int compare(const std::vector<unsigned char>& other) const
{ {
assert(mValue); assert(mValue);
return mValue->compare(other);
const auto valueSize = mValue->size();
const auto otherSize = other.size();
if (const auto result = std::memcmp(mValue->data(), other.data(), std::min(valueSize, otherSize)))
return result;
if (valueSize < otherSize)
return -1;
if (valueSize > otherSize)
return 1;
return 0;
} }
virtual bool isLess(const KeyView& other) const virtual bool isLess(const KeyView& other) const
@ -147,7 +162,7 @@ namespace DetourNavigator
} }
private: private:
const std::string* mValue = nullptr; const std::vector<unsigned char>* mValue = nullptr;
}; };
class RecastMeshKeyView : public KeyView class RecastMeshKeyView : public KeyView
@ -156,7 +171,7 @@ namespace DetourNavigator
RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections) RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
: mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {}
int compare(const std::string& other) const override; int compare(const std::vector<unsigned char>& other) const override;
bool isLess(const KeyView& other) const override bool isLess(const KeyView& other) const override
{ {

View file

@ -10,22 +10,22 @@ namespace DetourNavigator
{ {
public: public:
template <class T> template <class T>
explicit ObjectId(T* value) throw() explicit ObjectId(T* value) noexcept
: mValue(reinterpret_cast<std::size_t>(value)) : mValue(reinterpret_cast<std::size_t>(value))
{ {
} }
std::size_t value() const throw() std::size_t value() const noexcept
{ {
return mValue; return mValue;
} }
friend bool operator <(const ObjectId lhs, const ObjectId rhs) throw() friend bool operator <(const ObjectId lhs, const ObjectId rhs) noexcept
{ {
return lhs.mValue < rhs.mValue; return lhs.mValue < rhs.mValue;
} }
friend bool operator ==(const ObjectId lhs, const ObjectId rhs) throw() friend bool operator ==(const ObjectId lhs, const ObjectId rhs) noexcept
{ {
return lhs.mValue == rhs.mValue; return lhs.mValue == rhs.mValue;
} }
@ -40,7 +40,7 @@ namespace std
template <> template <>
struct hash<DetourNavigator::ObjectId> struct hash<DetourNavigator::ObjectId>
{ {
std::size_t operator ()(const DetourNavigator::ObjectId value) const throw() std::size_t operator ()(const DetourNavigator::ObjectId value) const noexcept
{ {
return value.value(); return value.value();
} }

View file

@ -6,7 +6,6 @@
#include <vector> #include <vector>
#include <stdint.h> #include <stdint.h>
#include <string.h>
namespace ESM namespace ESM
{ {
@ -57,11 +56,16 @@ public:
} }
bool operator!=(const std::string& str) const { return !( (*this) == str ); } bool operator!=(const std::string& str) const { return !( (*this) == str ); }
size_t data_size() const { return size; } static size_t data_size() { return size; }
size_t length() const { return strnlen(self()->ro_data(), size); } size_t length() const { return strnlen(self()->ro_data(), size); }
std::string toString() const { return std::string(self()->ro_data(), this->length()); } std::string toString() const { return std::string(self()->ro_data(), this->length()); }
void assign(const std::string& value) { std::strncpy(self()->rw_data(), value.c_str(), size); } void assign(const std::string& value)
{
std::strncpy(self()->rw_data(), value.c_str(), size-1);
self()->rw_data()[size-1] = '\0';
}
void clear() { this->assign(""); } void clear() { this->assign(""); }
private: private:
DERIVED<size> const* self() const DERIVED<size> const* self() const
@ -103,6 +107,20 @@ struct FIXED_STRING<4> : public FIXED_STRING_BASE<FIXED_STRING, 4>
bool operator==(uint32_t v) const { return v == intval; } bool operator==(uint32_t v) const { return v == intval; }
bool operator!=(uint32_t v) const { return v != intval; } bool operator!=(uint32_t v) const { return v != intval; }
void assign(const std::string& value)
{
intval = 0;
size_t length = value.size();
if (length == 0) return;
data[0] = value[0];
if (length == 1) return;
data[1] = value[1];
if (length == 2) return;
data[2] = value[2];
if (length == 3) return;
data[3] = value[3];
}
char const* ro_data() const { return data; } char const* ro_data() const { return data; }
char* rw_data() { return data; } char* rw_data() { return data; }
}; };
@ -110,7 +128,6 @@ struct FIXED_STRING<4> : public FIXED_STRING_BASE<FIXED_STRING, 4>
typedef FIXED_STRING<4> NAME; typedef FIXED_STRING<4> NAME;
typedef FIXED_STRING<32> NAME32; typedef FIXED_STRING<32> NAME32;
typedef FIXED_STRING<64> NAME64; typedef FIXED_STRING<64> NAME64;
typedef FIXED_STRING<256> NAME256;
/* This struct defines a file 'context' which can be saved and later /* This struct defines a file 'context' which can be saved and later
restored by an ESMReader instance. It will save the position within restored by an ESMReader instance. It will save the position within

View file

@ -3,6 +3,8 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
#include <components/misc/stringops.hpp>
void ESM::InventoryState::load (ESMReader &esm) void ESM::InventoryState::load (ESMReader &esm)
{ {
// obsolete // obsolete
@ -106,6 +108,19 @@ void ESM::InventoryState::load (ESMReader &esm)
mSelectedEnchantItem = -1; mSelectedEnchantItem = -1;
esm.getHNOT(mSelectedEnchantItem, "SELE"); esm.getHNOT(mSelectedEnchantItem, "SELE");
// Old saves had restocking levelled items in a special map
// This turns items from that map into negative quantities
for(const auto& entry : mLevelledItemMap)
{
const std::string& id = entry.first.first;
const int count = entry.second;
for(auto& item : mItems)
{
if(item.mCount == count && Misc::StringUtils::ciEqual(id, item.mRef.mRefID))
item.mCount = -count;
}
}
} }
void ESM::InventoryState::save (ESMWriter &esm) const void ESM::InventoryState::save (ESMWriter &esm) const

View file

@ -4,7 +4,7 @@
#include "esmwriter.hpp" #include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 14; int ESM::SavedGame::sCurrentFormat = 15;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {

View file

@ -0,0 +1,51 @@
#ifndef OPENMW_BARRIER_H
#define OPENMW_BARRIER_H
#include <condition_variable>
#include <functional>
#include <mutex>
namespace Misc
{
/// @brief Synchronize several threads
class Barrier
{
public:
using BarrierCallback = std::function<void(void)>;
/// @param count number of threads to wait on
/// @param func callable to be executed once after all threads have met
Barrier(int count, BarrierCallback&& func) : mThreadCount(count), mRendezvousCount(0), mGeneration(0)
, mFunc(std::forward<BarrierCallback>(func))
{}
/// @brief stop execution of threads until count distinct threads reach this point
void wait()
{
std::unique_lock<std::mutex> lock(mMutex);
++mRendezvousCount;
const int currentGeneration = mGeneration;
if (mRendezvousCount == mThreadCount)
{
++mGeneration;
mRendezvousCount = 0;
mFunc();
mRendezvous.notify_all();
}
else
{
mRendezvous.wait(lock, [&]() { return mGeneration != currentGeneration; });
}
}
private:
int mThreadCount;
int mRendezvousCount;
int mGeneration;
mutable std::mutex mMutex;
std::condition_variable mRendezvous;
BarrierCallback mFunc;
};
}
#endif

View file

@ -3,29 +3,44 @@
#include <chrono> #include <chrono>
#include <random> #include <random>
namespace
{
Misc::Rng::Seed sSeed;
}
namespace Misc namespace Misc
{ {
std::mt19937 Rng::generator = std::mt19937(); Rng::Seed::Seed() {}
Rng::Seed::Seed(unsigned int seed)
{
mGenerator.seed(seed);
}
Rng::Seed& Rng::getSeed()
{
return sSeed;
}
void Rng::init(unsigned int seed) void Rng::init(unsigned int seed)
{ {
generator.seed(seed); sSeed.mGenerator.seed(seed);
} }
float Rng::rollProbability() float Rng::rollProbability(Seed& seed)
{ {
return std::uniform_real_distribution<float>(0, 1 - std::numeric_limits<float>::epsilon())(generator); return std::uniform_real_distribution<float>(0, 1 - std::numeric_limits<float>::epsilon())(sSeed.mGenerator);
} }
float Rng::rollClosedProbability() float Rng::rollClosedProbability(Seed& seed)
{ {
return std::uniform_real_distribution<float>(0, 1)(generator); return std::uniform_real_distribution<float>(0, 1)(sSeed.mGenerator);
} }
int Rng::rollDice(int max) int Rng::rollDice(int max, Seed& seed)
{ {
return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(generator) : 0; return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(sSeed.mGenerator) : 0;
} }
unsigned int Rng::generateDefaultSeed() unsigned int Rng::generateDefaultSeed()

View file

@ -13,24 +13,32 @@ namespace Misc
class Rng class Rng
{ {
public: public:
class Seed
{
std::mt19937 mGenerator;
public:
Seed();
Seed(const Seed&) = delete;
Seed(unsigned int seed);
friend class Rng;
};
/// create a RNG static Seed& getSeed();
static std::mt19937 generator;
/// seed the RNG /// seed the RNG
static void init(unsigned int seed = generateDefaultSeed()); static void init(unsigned int seed = generateDefaultSeed());
/// return value in range [0.0f, 1.0f) <- note open upper range. /// return value in range [0.0f, 1.0f) <- note open upper range.
static float rollProbability(); static float rollProbability(Seed& seed = getSeed());
/// return value in range [0.0f, 1.0f] <- note closed upper range. /// return value in range [0.0f, 1.0f] <- note closed upper range.
static float rollClosedProbability(); static float rollClosedProbability(Seed& seed = getSeed());
/// return value in range [0, max) <- note open upper range. /// return value in range [0, max) <- note open upper range.
static int rollDice(int max); static int rollDice(int max, Seed& seed = getSeed());
/// return value in range [0, 99] /// return value in range [0, 99]
static int roll0to99() { return rollDice(100); } static int roll0to99(Seed& seed = getSeed()) { return rollDice(100, seed); }
/// returns default seed for RNG /// returns default seed for RNG
static unsigned int generateDefaultSeed(); static unsigned int generateDefaultSeed();

View file

@ -14,12 +14,18 @@ namespace Nif
class Extra : public Record class Extra : public Record
{ {
public: public:
std::string name;
ExtraPtr next; // Next extra data record in the list ExtraPtr next; // Next extra data record in the list
void read(NIFStream *nif) void read(NIFStream *nif)
{ {
next.read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->getUInt(); // Size of the record name = nif->getString();
else if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
{
next.read(nif);
nif->getUInt(); // Size of the record
}
} }
void post(NIFFile *nif) { next.post(nif); } void post(NIFFile *nif) { next.post(nif); }
@ -44,18 +50,23 @@ class Named : public Record
public: public:
std::string name; std::string name;
ExtraPtr extra; ExtraPtr extra;
ExtraList extralist;
ControllerPtr controller; ControllerPtr controller;
void read(NIFStream *nif) void read(NIFStream *nif)
{ {
name = nif->getString(); name = nif->getString();
extra.read(nif); if (nif->getVersion() < NIFStream::generateVersion(10,0,1,0))
extra.read(nif);
else
extralist.read(nif);
controller.read(nif); controller.read(nif);
} }
void post(NIFFile *nif) void post(NIFFile *nif)
{ {
extra.post(nif); extra.post(nif);
extralist.post(nif);
controller.post(nif); controller.post(nif);
} }
}; };

View file

@ -14,16 +14,31 @@ namespace Nif
if (external) if (external)
filename = nif->getString(); filename = nif->getString();
else else
internal = nif->getChar(); {
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3))
if (!external && internal) internal = nif->getChar();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
filename = nif->getString(); // Original file path of the internal texture
}
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3))
{
if (!external && internal)
data.read(nif);
}
else
{
data.read(nif); data.read(nif);
}
pixel = nif->getUInt(); pixel = nif->getUInt();
mipmap = nif->getUInt(); mipmap = nif->getUInt();
alpha = nif->getUInt(); alpha = nif->getUInt();
nif->getChar(); // always 1 nif->getChar(); // always 1
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103))
nif->getBoolean(); // Direct rendering
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4))
nif->getBoolean(); // NiPersistentSrcTextureRendererData is used instead of NiPixelData
} }
void NiSourceTexture::post(NIFFile *nif) void NiSourceTexture::post(NIFFile *nif)
@ -79,6 +94,12 @@ namespace Nif
NiParticleModifier::read(nif); NiParticleModifier::read(nif);
mBounceFactor = nif->getFloat(); mBounceFactor = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(4,2,2,0))
{
// Unused in NifSkope. Need to figure out what these do.
/*bool spawnOnCollision = */nif->getBoolean();
/*bool dieOnCollision = */nif->getBoolean();
}
} }
void NiPlanarCollider::read(NIFStream *nif) void NiPlanarCollider::read(NIFStream *nif)

View file

@ -97,7 +97,10 @@ namespace Nif
// 01: Diffuse // 01: Diffuse
// 10: Specular // 10: Specular
// 11: Emissive // 11: Emissive
targetColor = (flags >> 4) & 3; if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
targetColor = nif->getUShort() & 3;
else
targetColor = (flags >> 4) & 3;
data.read(nif); data.read(nif);
} }
@ -110,6 +113,8 @@ namespace Nif
void NiLookAtController::read(NIFStream *nif) void NiLookAtController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
lookAtFlags = nif->getUShort();
target.read(nif); target.read(nif);
} }
@ -165,25 +170,13 @@ namespace Nif
data.post(nif); data.post(nif);
} }
void NiAlphaController::read(NIFStream *nif) void NiFloatInterpController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
data.read(nif); data.read(nif);
} }
void NiAlphaController::post(NIFFile *nif) void NiFloatInterpController::post(NIFFile *nif)
{
Controller::post(nif);
data.post(nif);
}
void NiRollController::read(NIFStream *nif)
{
Controller::read(nif);
data.read(nif);
}
void NiRollController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
data.post(nif); data.post(nif);
@ -192,6 +185,8 @@ namespace Nif
void NiGeomMorpherController::read(NIFStream *nif) void NiGeomMorpherController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_OB_OLD)
/*bool updateNormals = !!*/nif->getUShort();
data.read(nif); data.read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW)
/*bool alwaysActive = */nif->getChar(); // Always 0 /*bool alwaysActive = */nif->getChar(); // Always 0
@ -219,8 +214,11 @@ namespace Nif
{ {
Controller::read(nif); Controller::read(nif);
mTexSlot = nif->getUInt(); mTexSlot = nif->getUInt();
/*unknown=*/nif->getUInt();/*0?*/ if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
mDelta = nif->getFloat(); {
timeStart = nif->getFloat();
mDelta = nif->getFloat();
}
mSources.read(nif); mSources.read(nif);
} }

View file

@ -118,6 +118,7 @@ class NiLookAtController : public Controller
{ {
public: public:
NodePtr target; NodePtr target;
unsigned short lookAtFlags{0};
void read(NIFStream *nif); void read(NIFStream *nif);
void post(NIFFile *nif); void post(NIFFile *nif);
@ -142,23 +143,16 @@ public:
void post(NIFFile *nif); void post(NIFFile *nif);
}; };
class NiAlphaController : public Controller struct NiFloatInterpController : public Controller
{ {
public:
NiFloatDataPtr data; NiFloatDataPtr data;
void read(NIFStream *nif); void read(NIFStream *nif);
void post(NIFFile *nif); void post(NIFFile *nif);
}; };
class NiRollController : public Controller class NiAlphaController : public NiFloatInterpController { };
{ class NiRollController : public NiFloatInterpController { };
public:
NiFloatDataPtr data;
void read(NIFStream *nif);
void post(NIFFile *nif);
};
class NiGeomMorpherController : public Controller class NiGeomMorpherController : public Controller
{ {

View file

@ -33,13 +33,33 @@ void NiSkinInstance::post(NIFFile *nif)
void NiGeometryData::read(NIFStream *nif) void NiGeometryData::read(NIFStream *nif)
{ {
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,114))
nif->getInt(); // Group ID. (Almost?) always 0.
int verts = nif->getUShort(); int verts = nif->getUShort();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
nif->skip(2); // Keep flags and compress flags
if (nif->getBoolean()) if (nif->getBoolean())
nif->getVector3s(vertices, verts); nif->getVector3s(vertices, verts);
unsigned int dataFlags = 0;
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
dataFlags = nif->getUShort();
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->getUInt(); // Material CRC
if (nif->getBoolean()) if (nif->getBoolean())
{
nif->getVector3s(normals, verts); nif->getVector3s(normals, verts);
if (dataFlags & 0x1000)
{
nif->getVector3s(tangents, verts);
nif->getVector3s(bitangents, verts);
}
}
center = nif->getVector3(); center = nif->getVector3();
radius = nif->getFloat(); radius = nif->getFloat();
@ -47,14 +67,27 @@ void NiGeometryData::read(NIFStream *nif)
if (nif->getBoolean()) if (nif->getBoolean())
nif->getVector4s(colors, verts); nif->getVector4s(colors, verts);
// In Morrowind this field only corresponds to the number of UV sets. // Only the first 6 bits are used as a count. I think the rest are
// NifTools research is inaccurate. // flags of some sort.
int uvs = nif->getUShort(); unsigned int numUVs = dataFlags;
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
if(nif->getInt())
{ {
uvlist.resize(uvs); numUVs = nif->getUShort();
for(int i = 0;i < uvs;i++) // In Morrowind this field only corresponds to the number of UV sets.
// NifTools research is inaccurate.
if (nif->getVersion() > NIFFile::NIFVersion::VER_MW)
numUVs &= 0x3f;
}
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0)
numUVs &= 0x1;
bool hasUVs = true;
if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW)
hasUVs = nif->getBoolean();
if (hasUVs)
{
uvlist.resize(numUVs);
for (unsigned int i = 0; i < numUVs; i++)
{ {
nif->getVector2s(uvlist[i], verts); nif->getVector2s(uvlist[i], verts);
// flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin
@ -64,6 +97,12 @@ void NiGeometryData::read(NIFStream *nif)
} }
} }
} }
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->getUShort(); // Consistency flags
if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4))
nif->skip(4); // Additional data
} }
void NiTriShapeData::read(NIFStream *nif) void NiTriShapeData::read(NIFStream *nif)
@ -75,13 +114,17 @@ void NiTriShapeData::read(NIFStream *nif)
// We have three times as many vertices as triangles, so this // We have three times as many vertices as triangles, so this
// is always equal to tris*3. // is always equal to tris*3.
int cnt = nif->getInt(); int cnt = nif->getInt();
nif->getUShorts(triangles, cnt); bool hasTriangles = true;
if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD)
hasTriangles = nif->getBoolean();
if (hasTriangles)
nif->getUShorts(triangles, cnt);
// Read the match list, which lists the vertices that are equal to // Read the match list, which lists the vertices that are equal to
// vertices. We don't actually need need this for anything, so // vertices. We don't actually need need this for anything, so
// just skip it. // just skip it.
int verts = nif->getUShort(); unsigned short verts = nif->getUShort();
for(int i=0;i < verts;i++) for (unsigned short i=0; i < verts; i++)
{ {
// Number of vertices matching vertex 'i' // Number of vertices matching vertex 'i'
int num = nif->getUShort(); int num = nif->getUShort();
@ -101,7 +144,11 @@ void NiTriStripsData::read(NIFStream *nif)
std::vector<unsigned short> lengths; std::vector<unsigned short> lengths;
nif->getUShorts(lengths, numStrips); nif->getUShorts(lengths, numStrips);
if (!numStrips) // "Has Strips" flag. Exceptionally useful.
bool hasStrips = true;
if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD)
hasStrips = nif->getBoolean();
if (!hasStrips || !numStrips)
return; return;
strips.resize(numStrips); strips.resize(numStrips);
@ -140,27 +187,37 @@ void NiAutoNormalParticlesData::read(NIFStream *nif)
NiGeometryData::read(nif); NiGeometryData::read(nif);
// Should always match the number of vertices // Should always match the number of vertices
numParticles = nif->getUShort(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW)
numParticles = nif->getUShort();
particleRadius = nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat());
else if (nif->getBoolean())
nif->getFloats(particleRadii, vertices.size());
activeCount = nif->getUShort(); activeCount = nif->getUShort();
// Particle sizes
if (nif->getBoolean()) if (nif->getBoolean())
{
// Particle sizes
nif->getFloats(sizes, vertices.size()); nif->getFloats(sizes, vertices.size());
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0) && nif->getBoolean())
nif->getQuaternions(rotations, vertices.size());
if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4))
{
if (nif->getBoolean())
nif->getFloats(rotationAngles, vertices.size());
if (nif->getBoolean())
nif->getVector3s(rotationAxes, vertices.size());
} }
} }
void NiRotatingParticlesData::read(NIFStream *nif) void NiRotatingParticlesData::read(NIFStream *nif)
{ {
NiAutoNormalParticlesData::read(nif); NiAutoNormalParticlesData::read(nif);
if (nif->getBoolean()) if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean())
{
// Rotation quaternions.
nif->getQuaternions(rotations, vertices.size()); nif->getQuaternions(rotations, vertices.size());
}
} }
void NiPosData::read(NIFStream *nif) void NiPosData::read(NIFStream *nif)
@ -188,12 +245,27 @@ void NiPixelData::read(NIFStream *nif)
{ {
fmt = (Format)nif->getUInt(); fmt = (Format)nif->getUInt();
for (unsigned int i = 0; i < 4; ++i) if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
colorMask[i] = nif->getUInt(); {
bpp = nif->getUInt(); for (unsigned int i = 0; i < 4; ++i)
colorMask[i] = nif->getUInt();
bpp = nif->getUInt();
nif->skip(8); // "Old Fast Compare". Whatever that means.
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
pixelTiling = nif->getUInt();
}
else // TODO: see if anything from here needs to be implemented
{
bpp = nif->getChar();
nif->skip(4); // Renderer hint
nif->skip(4); // Extra data
nif->skip(4); // Flags
pixelTiling = nif->getUInt();
if (nif->getVersion() >= NIFStream::generateVersion(20,3,0,4))
sRGB = nif->getBoolean();
nif->skip(4*10); // Channel data
}
// 8 bytes of "Old Fast Compare". Whatever that means.
nif->skip(8);
palette.read(nif); palette.read(nif);
numberOfMipmaps = nif->getUInt(); numberOfMipmaps = nif->getUInt();
@ -213,8 +285,10 @@ void NiPixelData::read(NIFStream *nif)
// Read the data // Read the data
unsigned int numPixels = nif->getUInt(); unsigned int numPixels = nif->getUInt();
if (numPixels) bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10,4,0,2);
nif->getUChars(data, numPixels); unsigned int numFaces = hasFaces ? nif->getUInt() : 1;
if (numPixels && numFaces)
nif->getUChars(data, numPixels * numFaces);
} }
void NiPixelData::post(NIFFile *nif) void NiPixelData::post(NIFFile *nif)
@ -249,6 +323,10 @@ void NiSkinData::read(NIFStream *nif)
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->skip(4); // NiSkinPartition link nif->skip(4); // NiSkinPartition link
// Has vertex weights flag
if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean())
return;
bones.resize(boneNum); bones.resize(boneNum);
for (BoneInfo &bi : bones) for (BoneInfo &bi : bones)
{ {
@ -272,7 +350,7 @@ void NiMorphData::read(NIFStream *nif)
{ {
int morphCount = nif->getInt(); int morphCount = nif->getInt();
int vertCount = nif->getInt(); int vertCount = nif->getInt();
/*relative targets?*/nif->getChar(); nif->getChar(); // Relative targets, always 1
mMorphs.resize(morphCount); mMorphs.resize(morphCount);
for(int i = 0;i < morphCount;i++) for(int i = 0;i < morphCount;i++)
@ -290,7 +368,8 @@ void NiKeyframeData::read(NIFStream *nif)
if(mRotations->mInterpolationType == InterpolationType_XYZ) if(mRotations->mInterpolationType == InterpolationType_XYZ)
{ {
//Chomp unused float //Chomp unused float
nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->getFloat();
mXRotations = std::make_shared<FloatKeyMap>(); mXRotations = std::make_shared<FloatKeyMap>();
mYRotations = std::make_shared<FloatKeyMap>(); mYRotations = std::make_shared<FloatKeyMap>();
mZRotations = std::make_shared<FloatKeyMap>(); mZRotations = std::make_shared<FloatKeyMap>();

View file

@ -35,7 +35,7 @@ namespace Nif
class NiGeometryData : public Record class NiGeometryData : public Record
{ {
public: public:
std::vector<osg::Vec3f> vertices, normals; std::vector<osg::Vec3f> vertices, normals, tangents, bitangents;
std::vector<osg::Vec4f> colors; std::vector<osg::Vec4f> colors;
std::vector< std::vector<osg::Vec2f> > uvlist; std::vector< std::vector<osg::Vec2f> > uvlist;
osg::Vec3f center; osg::Vec3f center;
@ -73,13 +73,13 @@ struct NiLinesData : public NiGeometryData
class NiAutoNormalParticlesData : public NiGeometryData class NiAutoNormalParticlesData : public NiGeometryData
{ {
public: public:
int numParticles; int numParticles{0};
float particleRadius;
int activeCount; int activeCount;
std::vector<float> sizes; std::vector<float> particleRadii, sizes, rotationAngles;
std::vector<osg::Quat> rotations;
std::vector<osg::Vec3f> rotationAxes;
void read(NIFStream *nif); void read(NIFStream *nif);
}; };
@ -87,8 +87,6 @@ public:
class NiRotatingParticlesData : public NiAutoNormalParticlesData class NiRotatingParticlesData : public NiAutoNormalParticlesData
{ {
public: public:
std::vector<osg::Quat> rotations;
void read(NIFStream *nif); void read(NIFStream *nif);
}; };
@ -133,7 +131,8 @@ public:
Format fmt; Format fmt;
unsigned int colorMask[4]; unsigned int colorMask[4];
unsigned int bpp; unsigned int bpp, pixelTiling{0};
bool sRGB{false};
NiPalettePtr palette; NiPalettePtr palette;
unsigned int numberOfMipmaps; unsigned int numberOfMipmaps;

View file

@ -28,6 +28,10 @@ void NiTextureEffect::read(NIFStream *nif)
// Texture Filtering // Texture Filtering
nif->skip(4); nif->skip(4);
// Max anisotropy samples
if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4))
nif->skip(2);
clamp = nif->getUInt(); clamp = nif->getUInt();
textureType = (TextureType)nif->getUInt(); textureType = (TextureType)nif->getUInt();
@ -36,14 +40,12 @@ void NiTextureEffect::read(NIFStream *nif)
texture.read(nif); texture.read(nif);
/* nif->skip(1); // Use clipping plane
byte = 0 nif->skip(16); // Clipping plane dimensions vector
vector4 = [1,0,0,0] if (nif->getVersion() <= NIFStream::generateVersion(10,2,0,0))
short = 0 nif->skip(4); // PS2-specific shorts
short = -75 if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,12))
short = 0 nif->skip(2); // Unknown short
*/
nif->skip(23);
} }
void NiTextureEffect::post(NIFFile *nif) void NiTextureEffect::post(NIFFile *nif)

View file

@ -34,6 +34,9 @@ struct NiDynamicEffect : public Node
void read(NIFStream *nif) void read(NIFStream *nif)
{ {
Node::read(nif); Node::read(nif);
if (nif->getVersion() >= nif->generateVersion(10,1,0,106)
&& nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
nif->getBoolean(); // Switch state
unsigned int numAffectedNodes = nif->getUInt(); unsigned int numAffectedNodes = nif->getUInt();
for (unsigned int i=0; i<numAffectedNodes; ++i) for (unsigned int i=0; i<numAffectedNodes; ++i)
nif->getUInt(); // ref to another Node nif->getUInt(); // ref to another Node

View file

@ -29,6 +29,56 @@ void NiVertWeightsExtraData::read(NIFStream *nif)
nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess
} }
void NiIntegerExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getUInt();
}
void NiIntegersExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int num = nif->getUInt();
if (num)
nif->getUInts(data, num);
}
void NiBinaryExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int size = nif->getUInt();
if (size)
nif->getChars(data, size);
}
void NiBooleanExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getBoolean();
}
void NiVectorExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getVector4();
}
void NiFloatExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getFloat();
}
void NiFloatsExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int num = nif->getUInt();
if (num)
nif->getFloats(data, num);
}
} }

View file

@ -60,5 +60,54 @@ public:
void read(NIFStream *nif); void read(NIFStream *nif);
}; };
struct NiIntegerExtraData : public Extra
{
unsigned int data;
void read(NIFStream *nif);
};
struct NiIntegersExtraData : public Extra
{
std::vector<unsigned int> data;
void read(NIFStream *nif);
};
struct NiBinaryExtraData : public Extra
{
std::vector<char> data;
void read(NIFStream *nif);
};
struct NiBooleanExtraData : public Extra
{
bool data;
void read(NIFStream *nif);
};
struct NiVectorExtraData : public Extra
{
osg::Vec4f data;
void read(NIFStream *nif);
};
struct NiFloatExtraData : public Extra
{
float data;
void read(NIFStream *nif);
};
struct NiFloatsExtraData : public Extra
{
std::vector<float> data;
void read(NIFStream *nif);
};
} // Namespace } // Namespace
#endif #endif

View file

@ -16,108 +16,106 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
NIFFile::~NIFFile() NIFFile::~NIFFile()
{ {
for (std::vector<Record*>::iterator it = records.begin() ; it != records.end(); ++it) for (Record* record : records)
{ delete record;
delete *it;
}
} }
template <typename NodeType> static Record* construct() { return new NodeType; } template <typename NodeType> static Record* construct() { return new NodeType; }
struct RecordFactoryEntry { struct RecordFactoryEntry {
typedef Record* (*create_t) (); using create_t = Record* (*)();
create_t mCreate; create_t mCreate;
RecordType mType; RecordType mType;
}; };
///Helper function for adding records to the factory map
static std::pair<std::string,RecordFactoryEntry> makeEntry(std::string recName, Record* (*create_t) (), RecordType type)
{
RecordFactoryEntry anEntry = {create_t,type};
return std::make_pair(recName, anEntry);
}
///These are all the record types we know how to read. ///These are all the record types we know how to read.
static std::map<std::string,RecordFactoryEntry> makeFactory() static std::map<std::string,RecordFactoryEntry> makeFactory()
{ {
std::map<std::string,RecordFactoryEntry> newFactory; std::map<std::string,RecordFactoryEntry> factory;
newFactory.insert(makeEntry("NiNode", &construct <NiNode> , RC_NiNode )); factory["NiNode"] = {&construct <NiNode> , RC_NiNode };
newFactory.insert(makeEntry("NiSwitchNode", &construct <NiSwitchNode> , RC_NiSwitchNode )); factory["NiSwitchNode"] = {&construct <NiSwitchNode> , RC_NiSwitchNode };
newFactory.insert(makeEntry("NiLODNode", &construct <NiLODNode> , RC_NiLODNode )); factory["NiLODNode"] = {&construct <NiLODNode> , RC_NiLODNode };
newFactory.insert(makeEntry("AvoidNode", &construct <NiNode> , RC_AvoidNode )); factory["AvoidNode"] = {&construct <NiNode> , RC_AvoidNode };
newFactory.insert(makeEntry("NiCollisionSwitch", &construct <NiNode> , RC_NiCollisionSwitch )); factory["NiCollisionSwitch"] = {&construct <NiNode> , RC_NiCollisionSwitch };
newFactory.insert(makeEntry("NiBSParticleNode", &construct <NiNode> , RC_NiBSParticleNode )); factory["NiBSParticleNode"] = {&construct <NiNode> , RC_NiBSParticleNode };
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode )); factory["NiBSAnimationNode"] = {&construct <NiNode> , RC_NiBSAnimationNode };
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode )); factory["NiBillboardNode"] = {&construct <NiNode> , RC_NiBillboardNode };
newFactory.insert(makeEntry("NiTriShape", &construct <NiTriShape> , RC_NiTriShape )); factory["NiTriShape"] = {&construct <NiTriShape> , RC_NiTriShape };
newFactory.insert(makeEntry("NiTriStrips", &construct <NiTriStrips> , RC_NiTriStrips )); factory["NiTriStrips"] = {&construct <NiTriStrips> , RC_NiTriStrips };
newFactory.insert(makeEntry("NiLines", &construct <NiLines> , RC_NiLines )); factory["NiLines"] = {&construct <NiLines> , RC_NiLines };
newFactory.insert(makeEntry("NiRotatingParticles", &construct <NiRotatingParticles> , RC_NiRotatingParticles )); factory["NiRotatingParticles"] = {&construct <NiRotatingParticles> , RC_NiRotatingParticles };
newFactory.insert(makeEntry("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles )); factory["NiAutoNormalParticles"] = {&construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles };
newFactory.insert(makeEntry("NiCamera", &construct <NiCamera> , RC_NiCamera )); factory["NiCamera"] = {&construct <NiCamera> , RC_NiCamera };
newFactory.insert(makeEntry("RootCollisionNode", &construct <NiNode> , RC_RootCollisionNode )); factory["RootCollisionNode"] = {&construct <NiNode> , RC_RootCollisionNode };
newFactory.insert(makeEntry("NiTexturingProperty", &construct <NiTexturingProperty> , RC_NiTexturingProperty )); factory["NiTexturingProperty"] = {&construct <NiTexturingProperty> , RC_NiTexturingProperty };
newFactory.insert(makeEntry("NiFogProperty", &construct <NiFogProperty> , RC_NiFogProperty )); factory["NiFogProperty"] = {&construct <NiFogProperty> , RC_NiFogProperty };
newFactory.insert(makeEntry("NiMaterialProperty", &construct <NiMaterialProperty> , RC_NiMaterialProperty )); factory["NiMaterialProperty"] = {&construct <NiMaterialProperty> , RC_NiMaterialProperty };
newFactory.insert(makeEntry("NiZBufferProperty", &construct <NiZBufferProperty> , RC_NiZBufferProperty )); factory["NiZBufferProperty"] = {&construct <NiZBufferProperty> , RC_NiZBufferProperty };
newFactory.insert(makeEntry("NiAlphaProperty", &construct <NiAlphaProperty> , RC_NiAlphaProperty )); factory["NiAlphaProperty"] = {&construct <NiAlphaProperty> , RC_NiAlphaProperty };
newFactory.insert(makeEntry("NiVertexColorProperty", &construct <NiVertexColorProperty> , RC_NiVertexColorProperty )); factory["NiVertexColorProperty"] = {&construct <NiVertexColorProperty> , RC_NiVertexColorProperty };
newFactory.insert(makeEntry("NiShadeProperty", &construct <NiShadeProperty> , RC_NiShadeProperty )); factory["NiShadeProperty"] = {&construct <NiShadeProperty> , RC_NiShadeProperty };
newFactory.insert(makeEntry("NiDitherProperty", &construct <NiDitherProperty> , RC_NiDitherProperty )); factory["NiDitherProperty"] = {&construct <NiDitherProperty> , RC_NiDitherProperty };
newFactory.insert(makeEntry("NiWireframeProperty", &construct <NiWireframeProperty> , RC_NiWireframeProperty )); factory["NiWireframeProperty"] = {&construct <NiWireframeProperty> , RC_NiWireframeProperty };
newFactory.insert(makeEntry("NiSpecularProperty", &construct <NiSpecularProperty> , RC_NiSpecularProperty )); factory["NiSpecularProperty"] = {&construct <NiSpecularProperty> , RC_NiSpecularProperty };
newFactory.insert(makeEntry("NiStencilProperty", &construct <NiStencilProperty> , RC_NiStencilProperty )); factory["NiStencilProperty"] = {&construct <NiStencilProperty> , RC_NiStencilProperty };
newFactory.insert(makeEntry("NiVisController", &construct <NiVisController> , RC_NiVisController )); factory["NiVisController"] = {&construct <NiVisController> , RC_NiVisController };
newFactory.insert(makeEntry("NiGeomMorpherController", &construct <NiGeomMorpherController> , RC_NiGeomMorpherController )); factory["NiGeomMorpherController"] = {&construct <NiGeomMorpherController> , RC_NiGeomMorpherController };
newFactory.insert(makeEntry("NiKeyframeController", &construct <NiKeyframeController> , RC_NiKeyframeController )); factory["NiKeyframeController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
newFactory.insert(makeEntry("NiAlphaController", &construct <NiAlphaController> , RC_NiAlphaController )); factory["NiAlphaController"] = {&construct <NiAlphaController> , RC_NiAlphaController };
newFactory.insert(makeEntry("NiRollController", &construct <NiRollController> , RC_NiRollController )); factory["NiRollController"] = {&construct <NiRollController> , RC_NiRollController };
newFactory.insert(makeEntry("NiUVController", &construct <NiUVController> , RC_NiUVController )); factory["NiUVController"] = {&construct <NiUVController> , RC_NiUVController };
newFactory.insert(makeEntry("NiPathController", &construct <NiPathController> , RC_NiPathController )); factory["NiPathController"] = {&construct <NiPathController> , RC_NiPathController };
newFactory.insert(makeEntry("NiMaterialColorController", &construct <NiMaterialColorController> , RC_NiMaterialColorController )); factory["NiMaterialColorController"] = {&construct <NiMaterialColorController> , RC_NiMaterialColorController };
newFactory.insert(makeEntry("NiBSPArrayController", &construct <NiBSPArrayController> , RC_NiBSPArrayController )); factory["NiBSPArrayController"] = {&construct <NiBSPArrayController> , RC_NiBSPArrayController };
newFactory.insert(makeEntry("NiParticleSystemController", &construct <NiParticleSystemController> , RC_NiParticleSystemController )); factory["NiParticleSystemController"] = {&construct <NiParticleSystemController> , RC_NiParticleSystemController };
newFactory.insert(makeEntry("NiFlipController", &construct <NiFlipController> , RC_NiFlipController )); factory["NiFlipController"] = {&construct <NiFlipController> , RC_NiFlipController };
newFactory.insert(makeEntry("NiAmbientLight", &construct <NiLight> , RC_NiLight )); factory["NiAmbientLight"] = {&construct <NiLight> , RC_NiLight };
newFactory.insert(makeEntry("NiDirectionalLight", &construct <NiLight> , RC_NiLight )); factory["NiDirectionalLight"] = {&construct <NiLight> , RC_NiLight };
newFactory.insert(makeEntry("NiPointLight", &construct <NiPointLight> , RC_NiLight )); factory["NiPointLight"] = {&construct <NiPointLight> , RC_NiLight };
newFactory.insert(makeEntry("NiSpotLight", &construct <NiSpotLight> , RC_NiLight )); factory["NiSpotLight"] = {&construct <NiSpotLight> , RC_NiLight };
newFactory.insert(makeEntry("NiTextureEffect", &construct <NiTextureEffect> , RC_NiTextureEffect )); factory["NiTextureEffect"] = {&construct <NiTextureEffect> , RC_NiTextureEffect };
newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData )); factory["NiVertWeightsExtraData"] = {&construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData };
newFactory.insert(makeEntry("NiTextKeyExtraData", &construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData )); factory["NiTextKeyExtraData"] = {&construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData };
newFactory.insert(makeEntry("NiStringExtraData", &construct <NiStringExtraData> , RC_NiStringExtraData )); factory["NiStringExtraData"] = {&construct <NiStringExtraData> , RC_NiStringExtraData };
newFactory.insert(makeEntry("NiGravity", &construct <NiGravity> , RC_NiGravity )); factory["NiGravity"] = {&construct <NiGravity> , RC_NiGravity };
newFactory.insert(makeEntry("NiPlanarCollider", &construct <NiPlanarCollider> , RC_NiPlanarCollider )); factory["NiPlanarCollider"] = {&construct <NiPlanarCollider> , RC_NiPlanarCollider };
newFactory.insert(makeEntry("NiSphericalCollider", &construct <NiSphericalCollider> , RC_NiSphericalCollider )); factory["NiSphericalCollider"] = {&construct <NiSphericalCollider> , RC_NiSphericalCollider };
newFactory.insert(makeEntry("NiParticleGrowFade", &construct <NiParticleGrowFade> , RC_NiParticleGrowFade )); factory["NiParticleGrowFade"] = {&construct <NiParticleGrowFade> , RC_NiParticleGrowFade };
newFactory.insert(makeEntry("NiParticleColorModifier", &construct <NiParticleColorModifier> , RC_NiParticleColorModifier )); factory["NiParticleColorModifier"] = {&construct <NiParticleColorModifier> , RC_NiParticleColorModifier };
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation )); factory["NiParticleRotation"] = {&construct <NiParticleRotation> , RC_NiParticleRotation };
newFactory.insert(makeEntry("NiFloatData", &construct <NiFloatData> , RC_NiFloatData )); factory["NiFloatData"] = {&construct <NiFloatData> , RC_NiFloatData };
newFactory.insert(makeEntry("NiTriShapeData", &construct <NiTriShapeData> , RC_NiTriShapeData )); factory["NiTriShapeData"] = {&construct <NiTriShapeData> , RC_NiTriShapeData };
newFactory.insert(makeEntry("NiTriStripsData", &construct <NiTriStripsData> , RC_NiTriStripsData )); factory["NiTriStripsData"] = {&construct <NiTriStripsData> , RC_NiTriStripsData };
newFactory.insert(makeEntry("NiLinesData", &construct <NiLinesData> , RC_NiLinesData )); factory["NiLinesData"] = {&construct <NiLinesData> , RC_NiLinesData };
newFactory.insert(makeEntry("NiVisData", &construct <NiVisData> , RC_NiVisData )); factory["NiVisData"] = {&construct <NiVisData> , RC_NiVisData };
newFactory.insert(makeEntry("NiColorData", &construct <NiColorData> , RC_NiColorData )); factory["NiColorData"] = {&construct <NiColorData> , RC_NiColorData };
newFactory.insert(makeEntry("NiPixelData", &construct <NiPixelData> , RC_NiPixelData )); factory["NiPixelData"] = {&construct <NiPixelData> , RC_NiPixelData };
newFactory.insert(makeEntry("NiMorphData", &construct <NiMorphData> , RC_NiMorphData )); factory["NiMorphData"] = {&construct <NiMorphData> , RC_NiMorphData };
newFactory.insert(makeEntry("NiKeyframeData", &construct <NiKeyframeData> , RC_NiKeyframeData )); factory["NiKeyframeData"] = {&construct <NiKeyframeData> , RC_NiKeyframeData };
newFactory.insert(makeEntry("NiSkinData", &construct <NiSkinData> , RC_NiSkinData )); factory["NiSkinData"] = {&construct <NiSkinData> , RC_NiSkinData };
newFactory.insert(makeEntry("NiUVData", &construct <NiUVData> , RC_NiUVData )); factory["NiUVData"] = {&construct <NiUVData> , RC_NiUVData };
newFactory.insert(makeEntry("NiPosData", &construct <NiPosData> , RC_NiPosData )); factory["NiPosData"] = {&construct <NiPosData> , RC_NiPosData };
newFactory.insert(makeEntry("NiRotatingParticlesData", &construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData )); factory["NiRotatingParticlesData"] = {&construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData };
newFactory.insert(makeEntry("NiAutoNormalParticlesData", &construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData )); factory["NiAutoNormalParticlesData"] = {&construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData };
newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper )); factory["NiSequenceStreamHelper"] = {&construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper };
newFactory.insert(makeEntry("NiSourceTexture", &construct <NiSourceTexture> , RC_NiSourceTexture )); factory["NiSourceTexture"] = {&construct <NiSourceTexture> , RC_NiSourceTexture };
newFactory.insert(makeEntry("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance )); factory["NiSkinInstance"] = {&construct <NiSkinInstance> , RC_NiSkinInstance };
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController )); factory["NiLookAtController"] = {&construct <NiLookAtController> , RC_NiLookAtController };
newFactory.insert(makeEntry("NiPalette", &construct <NiPalette> , RC_NiPalette )); factory["NiPalette"] = {&construct <NiPalette> , RC_NiPalette };
return newFactory; factory["NiIntegerExtraData"] = {&construct <NiIntegerExtraData> , RC_NiIntegerExtraData };
factory["NiIntegersExtraData"] = {&construct <NiIntegersExtraData> , RC_NiIntegersExtraData };
factory["NiBinaryExtraData"] = {&construct <NiBinaryExtraData> , RC_NiBinaryExtraData };
factory["NiBooleanExtraData"] = {&construct <NiBooleanExtraData> , RC_NiBooleanExtraData };
factory["NiVectorExtraData"] = {&construct <NiVectorExtraData> , RC_NiVectorExtraData };
factory["NiColorExtraData"] = {&construct <NiVectorExtraData> , RC_NiColorExtraData };
factory["NiFloatExtraData"] = {&construct <NiFloatExtraData> , RC_NiFloatExtraData };
factory["NiFloatsExtraData"] = {&construct <NiFloatsExtraData> , RC_NiFloatsExtraData };
return factory;
} }
///Make the factory map used for parsing the file ///Make the factory map used for parsing the file
static const std::map<std::string,RecordFactoryEntry> factories = makeFactory(); static const std::map<std::string,RecordFactoryEntry> factories = makeFactory();
@ -148,15 +146,77 @@ void NIFFile::parse(Files::IStreamPtr stream)
// It's not used by Morrowind assets but Morrowind supports it. // It's not used by Morrowind assets but Morrowind supports it.
if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW) if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW)
fail("Unsupported NIF version: " + printVersion(ver)); fail("Unsupported NIF version: " + printVersion(ver));
// NIF data endianness
if (ver >= NIFStream::generateVersion(20,0,0,4))
{
unsigned char endianness = nif.getChar();
if (endianness == 0)
fail("Big endian NIF files are unsupported");
}
// User version
if (ver > NIFStream::generateVersion(10,0,1,8))
userVer = nif.getUInt();
// Number of records // Number of records
size_t recNum = nif.getInt(); size_t recNum = nif.getUInt();
records.resize(recNum); records.resize(recNum);
// Bethesda stream header
// It contains Bethesda format version and (useless) export information
if (ver == VER_OB_OLD ||
(userVer >= 3 && ((ver == VER_OB || ver == VER_BGS)
|| (ver >= NIFStream::generateVersion(10,1,0,0) && ver <= NIFStream::generateVersion(20,0,0,4) && userVer <= 11))))
{
bethVer = nif.getUInt();
nif.getExportString(); // Author
if (bethVer > BETHVER_FO4)
nif.getUInt(); // Unknown
nif.getExportString(); // Process script
nif.getExportString(); // Export script
if (bethVer == BETHVER_FO4)
nif.getExportString(); // Max file path
}
std::vector<std::string> recTypes;
std::vector<unsigned short> recTypeIndices;
const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5,0,0,1);
if (hasRecTypeListings)
{
unsigned short recTypeNum = nif.getUShort();
if (recTypeNum) // Record type list
nif.getSizedStrings(recTypes, recTypeNum);
if (recNum) // Record type mapping for each record
nif.getUShorts(recTypeIndices, recNum);
if (ver >= NIFStream::generateVersion(5,0,0,6)) // Groups
{
if (ver >= NIFStream::generateVersion(20,1,0,1)) // String table
{
if (ver >= NIFStream::generateVersion(20,2,0,5) && recNum) // Record sizes
{
std::vector<unsigned int> recSizes; // Currently unused
nif.getUInts(recSizes, recNum);
}
unsigned int stringNum = nif.getUInt();
nif.getUInt(); // Max string length
if (stringNum)
nif.getSizedStrings(strings, stringNum);
}
std::vector<unsigned int> groups; // Currently unused
unsigned int groupNum = nif.getUInt();
if (groupNum)
nif.getUInts(groups, groupNum);
}
}
const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0);
for(size_t i = 0;i < recNum;i++) for(size_t i = 0;i < recNum;i++)
{ {
Record *r = nullptr; Record *r = nullptr;
std::string rec = nif.getString(); std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString();
if(rec.empty()) if(rec.empty())
{ {
std::stringstream error; std::stringstream error;
@ -164,6 +224,17 @@ void NIFFile::parse(Files::IStreamPtr stream)
fail(error.str()); fail(error.str());
} }
// Record separator. Some Havok records in Oblivion do not have it.
if (hasRecordSeparators && rec.compare(0, 3, "bhk"))
{
if (nif.getInt())
{
std::stringstream warning;
warning << "Record number " << i << " out of " << recNum << " is preceded by a non-zero separator.";
warn(warning.str());
}
}
std::map<std::string,RecordFactoryEntry>::const_iterator entry = factories.find(rec); std::map<std::string,RecordFactoryEntry>::const_iterator entry = factories.find(rec);
if (entry != factories.end()) if (entry != factories.end())
@ -201,8 +272,8 @@ void NIFFile::parse(Files::IStreamPtr stream)
} }
// Once parsing is done, do post-processing. // Once parsing is done, do post-processing.
for(size_t i=0; i<recNum; i++) for (Record* record : records)
records[i]->post(this); record->post(this);
} }
void NIFFile::setUseSkinning(bool skinning) void NIFFile::setUseSkinning(bool skinning)

View file

@ -25,18 +25,19 @@ namespace Nif
return t; return t;
} }
///Currently specific for 4.0.0.2 and earlier ///Booleans in 4.0.0.2 (Morrowind format) and earlier are 4 byte, while in 4.1.0.0+ they're 1 byte.
bool NIFStream::getBoolean() bool NIFStream::getBoolean()
{ {
return getInt() != 0; return getVersion() < generateVersion(4,1,0,0) ? getInt() != 0 : getChar() != 0;
} }
///Read in a string, either from the string table using the index (currently absent) or from the stream using the specified length ///Read in a string, either from the string table using the index or from the stream using the specified length
std::string NIFStream::getString() std::string NIFStream::getString()
{ {
return getSizedString(); return getVersion() < generateVersion(20,1,0,1) ? getSizedString() : file->getString(getUInt());
} }
// Convenience utility functions: get the versions of the currently read file // Convenience utility functions: get the versions of the currently read file
unsigned int NIFStream::getVersion() const { return file->getVersion(); } unsigned int NIFStream::getVersion() const { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); } unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); }

View file

@ -30,7 +30,7 @@ public:
PropertyList props; PropertyList props;
// Bounding box info // Bounding box info
bool hasBounds; bool hasBounds{false};
osg::Vec3f boundPos; osg::Vec3f boundPos;
Matrix3 boundRot; Matrix3 boundRot;
osg::Vec3f boundXYZ; // Box size osg::Vec3f boundXYZ; // Box size
@ -39,12 +39,15 @@ public:
{ {
Named::read(nif); Named::read(nif);
flags = nif->getUShort(); flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt();
trafo = nif->getTrafo(); trafo = nif->getTrafo();
velocity = nif->getVector3(); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
props.read(nif); velocity = nif->getVector3();
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
props.read(nif);
hasBounds = nif->getBoolean(); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
hasBounds = nif->getBoolean();
if(hasBounds) if(hasBounds)
{ {
nif->getInt(); // always 1 nif->getInt(); // always 1
@ -52,6 +55,9 @@ public:
boundRot = nif->getMatrix3(); boundRot = nif->getMatrix3();
boundXYZ = nif->getVector3(); boundXYZ = nif->getVector3();
} }
// Reference to the collision object in Gamebryo files.
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->skip(4);
parent = nullptr; parent = nullptr;
@ -102,7 +108,8 @@ struct NiNode : Node
{ {
Node::read(nif); Node::read(nif);
children.read(nif); children.read(nif);
effects.read(nif); if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
effects.read(nif);
// Discard transformations for the root node, otherwise some meshes // Discard transformations for the root node, otherwise some meshes
// occasionally get wrong orientation. Only for NiNode-s for now, but // occasionally get wrong orientation. Only for NiNode-s for now, but
@ -130,7 +137,39 @@ struct NiNode : Node
struct NiGeometry : Node struct NiGeometry : Node
{ {
struct MaterialData
{
std::vector<std::string> materialNames;
std::vector<int> materialExtraData;
unsigned int activeMaterial{0};
bool materialNeedsUpdate{false};
void read(NIFStream *nif)
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
return;
unsigned int numMaterials = 0;
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3))
numMaterials = nif->getBoolean(); // Has Shader
else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
numMaterials = nif->getUInt();
if (numMaterials)
{
nif->getStrings(materialNames, numMaterials);
nif->getInts(materialExtraData, numMaterials);
}
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
activeMaterial = nif->getUInt();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
{
materialNeedsUpdate = nif->getBoolean();
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->skip(8);
}
}
};
NiSkinInstancePtr skin; NiSkinInstancePtr skin;
MaterialData materialData;
}; };
struct NiTriShape : NiGeometry struct NiTriShape : NiGeometry
@ -149,6 +188,7 @@ struct NiTriShape : NiGeometry
Node::read(nif); Node::read(nif);
data.read(nif); data.read(nif);
skin.read(nif); skin.read(nif);
materialData.read(nif);
} }
void post(NIFFile *nif) void post(NIFFile *nif)
@ -170,6 +210,7 @@ struct NiTriStrips : NiGeometry
Node::read(nif); Node::read(nif);
data.read(nif); data.read(nif);
skin.read(nif); skin.read(nif);
materialData.read(nif);
} }
void post(NIFFile *nif) void post(NIFFile *nif)
@ -207,6 +248,8 @@ struct NiCamera : Node
{ {
struct Camera struct Camera
{ {
unsigned short cameraFlags{0};
// Camera frustrum // Camera frustrum
float left, right, top, bottom, nearDist, farDist; float left, right, top, bottom, nearDist, farDist;
@ -216,15 +259,21 @@ struct NiCamera : Node
// Level of detail modifier // Level of detail modifier
float LOD; float LOD;
// Orthographic projection usage flag
bool orthographic{false};
void read(NIFStream *nif) void read(NIFStream *nif)
{ {
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
cameraFlags = nif->getUShort();
left = nif->getFloat(); left = nif->getFloat();
right = nif->getFloat(); right = nif->getFloat();
top = nif->getFloat(); top = nif->getFloat();
bottom = nif->getFloat(); bottom = nif->getFloat();
nearDist = nif->getFloat(); nearDist = nif->getFloat();
farDist = nif->getFloat(); farDist = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
orthographic = nif->getBoolean();
vleft = nif->getFloat(); vleft = nif->getFloat();
vright = nif->getFloat(); vright = nif->getFloat();
vtop = nif->getFloat(); vtop = nif->getFloat();
@ -243,6 +292,8 @@ struct NiCamera : Node
nif->getInt(); // -1 nif->getInt(); // -1
nif->getInt(); // 0 nif->getInt(); // 0
if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0))
nif->getInt(); // 0
} }
}; };
@ -285,11 +336,14 @@ struct NiRotatingParticles : Node
// A node used as the base to switch between child nodes, such as for LOD levels. // A node used as the base to switch between child nodes, such as for LOD levels.
struct NiSwitchNode : public NiNode struct NiSwitchNode : public NiNode
{ {
unsigned int switchFlags{0};
unsigned int initialIndex; unsigned int initialIndex;
void read(NIFStream *nif) void read(NIFStream *nif)
{ {
NiNode::read(nif); NiNode::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
switchFlags = nif->getUShort();
initialIndex = nif->getUInt(); initialIndex = nif->getUInt();
} }
}; };
@ -310,6 +364,12 @@ struct NiLODNode : public NiSwitchNode
NiSwitchNode::read(nif); NiSwitchNode::read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
lodCenter = nif->getVector3(); lodCenter = nif->getVector3();
else if (nif->getVersion() > NIFStream::generateVersion(10,0,1,0))
{
nif->skip(4); // NiLODData, unsupported at the moment
return;
}
unsigned int numLodLevels = nif->getUInt(); unsigned int numLodLevels = nif->getUInt();
for (unsigned int i=0; i<numLodLevels; ++i) for (unsigned int i=0; i<numLodLevels; ++i)
{ {

View file

@ -6,25 +6,44 @@
namespace Nif namespace Nif
{ {
void Property::read(NIFStream *nif)
{
Named::read(nif);
flags = nif->getUShort();
}
void NiTexturingProperty::Texture::read(NIFStream *nif) void NiTexturingProperty::Texture::read(NIFStream *nif)
{ {
inUse = nif->getBoolean(); inUse = nif->getBoolean();
if(!inUse) return; if(!inUse) return;
texture.read(nif); texture.read(nif);
clamp = nif->getUInt(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible {
uvSet = nif->getUInt(); clamp = nif->getInt();
nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible
}
else
{
clamp = nif->getUShort() & 0xF;
}
// Max anisotropy. I assume we'll always only use the global anisotropy setting.
if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4))
nif->getUShort();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
uvSet = nif->getUInt();
// Two PS2-specific shorts. // Two PS2-specific shorts.
nif->skip(4); if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
nif->skip(2); // Unknown short nif->skip(4);
if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,18))
nif->skip(2); // Unknown short
else if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
{
if (nif->getBoolean()) // Has texture transform
{
nif->getVector2(); // UV translation
nif->getVector2(); // UV scale
nif->getFloat(); // W axis rotation
nif->getUInt(); // Transform method
nif->getVector2(); // Texture rotation origin
}
}
} }
void NiTexturingProperty::Texture::post(NIFFile *nif) void NiTexturingProperty::Texture::post(NIFFile *nif)
@ -35,7 +54,10 @@ void NiTexturingProperty::Texture::post(NIFFile *nif)
void NiTexturingProperty::read(NIFStream *nif) void NiTexturingProperty::read(NIFStream *nif)
{ {
Property::read(nif); Property::read(nif);
apply = nif->getUInt(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20,1,0,2))
flags = nif->getUShort();
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,1))
apply = nif->getUInt();
unsigned int numTextures = nif->getUInt(); unsigned int numTextures = nif->getUInt();
@ -51,32 +73,53 @@ void NiTexturingProperty::read(NIFStream *nif)
envMapLumaBias = nif->getVector2(); envMapLumaBias = nif->getVector2();
bumpMapMatrix = nif->getVector4(); bumpMapMatrix = nif->getVector4();
} }
else if (i == 7 && textures[7].inUse && nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
/*float parallaxOffset = */nif->getFloat();
}
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
{
unsigned int numShaderTextures = nif->getUInt();
shaderTextures.resize(numShaderTextures);
for (unsigned int i = 0; i < numShaderTextures; i++)
{
shaderTextures[i].read(nif);
if (shaderTextures[i].inUse)
nif->getUInt(); // Unique identifier
}
} }
} }
void NiTexturingProperty::post(NIFFile *nif) void NiTexturingProperty::post(NIFFile *nif)
{ {
Property::post(nif); Property::post(nif);
for(int i = 0;i < 7;i++) for (size_t i = 0; i < textures.size(); i++)
textures[i].post(nif); textures[i].post(nif);
for (size_t i = 0; i < shaderTextures.size(); i++)
shaderTextures[i].post(nif);
} }
void NiFogProperty::read(NIFStream *nif) void NiFogProperty::read(NIFStream *nif)
{ {
Property::read(nif); Property::read(nif);
mFlags = nif->getUShort();
mFogDepth = nif->getFloat(); mFogDepth = nif->getFloat();
mColour = nif->getVector3(); mColour = nif->getVector3();
} }
void S_MaterialProperty::read(NIFStream *nif) void S_MaterialProperty::read(NIFStream *nif)
{ {
ambient = nif->getVector3(); if (nif->getBethVersion() < 26)
diffuse = nif->getVector3(); {
ambient = nif->getVector3();
diffuse = nif->getVector3();
}
specular = nif->getVector3(); specular = nif->getVector3();
emissive = nif->getVector3(); emissive = nif->getVector3();
glossiness = nif->getFloat(); glossiness = nif->getFloat();
alpha = nif->getFloat(); alpha = nif->getFloat();
if (nif->getBethVersion() > 21)
emissive *= nif->getFloat();
} }
void S_VertexColorProperty::read(NIFStream *nif) void S_VertexColorProperty::read(NIFStream *nif)
@ -92,14 +135,29 @@ void S_AlphaProperty::read(NIFStream *nif)
void S_StencilProperty::read(NIFStream *nif) void S_StencilProperty::read(NIFStream *nif)
{ {
enabled = nif->getChar(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
compareFunc = nif->getInt(); {
stencilRef = nif->getUInt(); enabled = nif->getChar();
stencilMask = nif->getUInt(); compareFunc = nif->getInt();
failAction = nif->getInt(); stencilRef = nif->getUInt();
zFailAction = nif->getInt(); stencilMask = nif->getUInt();
zPassAction = nif->getInt(); failAction = nif->getInt();
drawMode = nif->getInt(); zFailAction = nif->getInt();
zPassAction = nif->getInt();
drawMode = nif->getInt();
}
else
{
unsigned short flags = nif->getUShort();
enabled = flags & 0x1;
failAction = (flags >> 1) & 0x7;
zFailAction = (flags >> 4) & 0x7;
zPassAction = (flags >> 7) & 0x7;
drawMode = (flags >> 10) & 0x3;
compareFunc = (flags >> 12) & 0x7;
stencilRef = nif->getUInt();
stencilMask = nif->getUInt();
}
} }

Some files were not shown because too many files have changed in this diff Show more