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:
commit
68837aaf4a
115 changed files with 3064 additions and 1278 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
62
CI/before_script.linux.sh
Executable file → Normal 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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}"
|
||||||
|
|
354
CMakeLists.txt
354
CMakeLists.txt
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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(), ""));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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&);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
607
apps/openmw/mwphysics/mtphysics.cpp
Normal file
607
apps/openmw/mwphysics/mtphysics.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
104
apps/openmw/mwphysics/mtphysics.hpp
Normal file
104
apps/openmw/mwphysics/mtphysics.hpp
Normal 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
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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()};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
51
components/misc/barrier.hpp
Normal file
51
components/misc/barrier.hpp
Normal 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
|
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue