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

Add OpenMW commits up to 15 Oct 2020

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

View file

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

View file

@ -74,7 +74,7 @@ script:
- 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 ../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
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s

View file

@ -5,14 +5,18 @@
Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
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 #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 #4055: Local scripts don't inherit variables from their base record
Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem
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 #5165: Active spells should use real time intead of timestamps
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 #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 #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 #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 #2386: Distant Statics in the form of Object Paging
Feature #4894: Consider actors as obstacles for pathfinding
@ -61,8 +67,10 @@
Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8)
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 #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 #5520: Improve cell name autocompleter implementation

View file

@ -1,6 +1,9 @@
#!/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
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

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

@ -2,8 +2,10 @@
free -m
if [[ "${BUILD_TESTS_ONLY}" ]]; then
export GOOGLETEST_DIR="$(pwd)/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
GOOGLETEST_DIR="$(pwd)/googletest/build"
fi
mkdir build
cd build
@ -15,22 +17,40 @@ fi
export RAKNET_ROOT=~/CrabNet
${ANALYZE} cmake .. \
if [[ "${BUILD_TESTS_ONLY}" ]]; then
${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 CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D USE_SYSTEM_TINYXML=TRUE \
-D BUILD_OPENMW=OFF \
-D BUILD_OPENMW_MP=OFF \
-D BUILD_BSATOOL=OFF \
-D BUILD_ESMTOOL=OFF \
-D BUILD_LAUNCHER=OFF \
-D BUILD_BROWSER=OFF \
-D BUILD_MWINIIMPORTER=OFF \
-D BUILD_ESSIMPORTER=OFF \
-D BUILD_OPENCS=OFF \
-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 \
-DUSE_SYSTEM_TINYXML=1 \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBINDIR=/usr/games \
-DCMAKE_BUILD_TYPE="None" \
-DBUILD_UNITTESTS=TRUE \
-D USE_SYSTEM_TINYXML=TRUE \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DBINDIR="/usr/games" \
-DCMAKE_BUILD_TYPE="DEBUG" \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=Debug \
-D RakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
-D RakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
..
fi

View file

@ -523,7 +523,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost
if [ -z $APPVEYOR ]; then
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"
fi

View file

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" \
-D CMAKE_C_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 OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \

View file

@ -1,13 +1,17 @@
#!/bin/sh -e
#!/bin/sh -ex
git clone -b release-1.10.0 https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
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_INSTALL_PREFIX=. \
-D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
-G "${GENERATOR}" \
..
cmake --build . --config "${CONFIGURATION}"
cmake --build . --target install --config "${CONFIGURATION}"
cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
cmake --install . --config "${CONFIGURATION}"

View file

@ -458,172 +458,6 @@ elseif (MSVC)
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)
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
IF(BUILD_OPENMW OR BUILD_OPENCS)
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
@ -938,8 +772,194 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\")
" COMPONENT Runtime)
include(CPack)
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'
# output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen"
# output directory for 'make doc_pages' is "${DOXYGEN_PAGES_OUTPUT_DIR}" if defined

View file

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

View file

@ -233,8 +233,15 @@ target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)
if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
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()
if (MSVC)

View file

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

View file

@ -6,6 +6,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/magiceffects.hpp"
@ -79,6 +80,7 @@ namespace MWClass
float weight = getContainerStore(ptr).getWeight();
const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects();
weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).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;
}

View file

@ -43,44 +43,41 @@
namespace MWClass
{
class ContainerCustomData : public MWWorld::CustomData
ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell)
{
public:
MWWorld::ContainerStore mContainerStore;
virtual MWWorld::CustomData *clone() const;
virtual ContainerCustomData& asContainerCustomData()
{
return *this;
unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max());
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
mStore.fillNonRandom(container.mInventory, "", seed);
}
virtual const ContainerCustomData& asContainerCustomData() const
ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory)
{
return *this;
mStore.readState(inventory);
}
};
MWWorld::CustomData *ContainerCustomData::clone() const
{
return new ContainerCustomData (*this);
}
ContainerCustomData& ContainerCustomData::asContainerCustomData()
{
return *this;
}
const ContainerCustomData& ContainerCustomData::asContainerCustomData() const
{
return *this;
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{
if (!ptr.getRefData().getCustomData())
{
std::unique_ptr<ContainerCustomData> data (new ContainerCustomData);
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, "");
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
// 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());
}
@ -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
{
if (!model.empty()) {
@ -292,12 +278,12 @@ namespace MWClass
return !name.empty() ? name : ref->mBase->mId;
}
MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr)
const
MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const
{
ensureCustomData (ptr);
return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore;
auto& data = ptr.getRefData().getCustomData()->asContainerCustomData();
data.mStore.mPtr = ptr;
return data.mStore;
}
std::string Container::getScript (const MWWorld::ConstPtr& ptr) const
@ -317,8 +303,7 @@ namespace MWClass
bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const
{
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems();
return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems();
return true;
}
@ -381,16 +366,8 @@ namespace MWClass
if (!state.mHasCustomState)
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();
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
@ -402,7 +379,13 @@ namespace MWClass
}
const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
if (!customData.mStore.isResolved())
{
state.mHasCustomState = false;
return;
}
ESM::ContainerState& containerState = state.asContainerState();
customData.mContainerStore.writeState (containerState.mInventory);
customData.mStore.writeState (containerState.mInventory);
}
}

View file

@ -2,9 +2,32 @@
#define GAME_MWCLASS_CONTAINER_H
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/customdata.hpp"
namespace ESM
{
struct Container;
struct InventoryState;
}
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
{
void ensureCustomData (const MWWorld::Ptr& ptr) const;
@ -70,8 +93,6 @@ namespace MWClass
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 bool useAnim() const;

View file

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

View file

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

View file

@ -1164,7 +1164,8 @@ namespace MWClass
// TODO: This function is called several times per frame for each NPC.
// 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);
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;
const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -1213,7 +1214,8 @@ namespace MWClass
return 0.f;
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;
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
{
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();

View file

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

View file

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

View file

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

View file

@ -52,22 +52,29 @@ namespace
namespace MWGui
{
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
{
if (mItemSources.size() == 0)
if (mItemSources.empty())
return true;
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
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)
@ -106,8 +113,9 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip)
{
const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1];
if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source))
auto& source = mItemSources[0];
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!");
/*
@ -118,10 +126,10 @@ MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count,
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;
objectList->cell = *source.getCell()->getCell();
objectList->cell = *source.first.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::ADD;
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->addBaseObject(baseObject);
objectList->sendContainer();
@ -146,9 +154,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t 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)
{
@ -164,16 +172,16 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
*/
mwmp::CurrentContainer *currentContainer = &mwmp::Main::get().getLocalPlayer()->currentContainer;
if (currentContainer->refNum != source.getCellRef().getRefNum().mIndex ||
currentContainer->mpNum != source.getCellRef().getMpNum())
if (currentContainer->refNum != source.first.getCellRef().getRefNum().mIndex ||
currentContainer->mpNum != source.first.getCellRef().getMpNum())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;
objectList->cell = *source.getCell()->getCell();
objectList->cell = *source.first.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::REMOVE;
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->addBaseObject(baseObject);
objectList->sendContainer();
@ -181,7 +189,14 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
toRemove -= it->getRefData().getCount();
}
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)
*/
@ -229,9 +244,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
void ContainerItemModel::update()
{
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)
{
@ -285,7 +300,7 @@ bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count)
if (mItemSources.empty())
return false;
MWWorld::Ptr target = mItemSources[0];
MWWorld::Ptr target = mItemSources[0].first;
if (target.getTypeName() != typeid(ESM::Container).name())
return true;
@ -315,7 +330,7 @@ bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count)
if (mItemSources.empty())
return false;
MWWorld::Ptr target = mItemSources[0];
MWWorld::Ptr target = mItemSources[0].first;
// Looting a dead corpse is considered OK
if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead())

View file

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

View file

@ -817,7 +817,8 @@ namespace MWGui
return;
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;
ItemModel::ModelIndex selected = -1;

View file

@ -414,7 +414,8 @@ namespace MWGui
|| playerStats.getKnockedDown()
|| 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)
{

View file

@ -272,8 +272,9 @@ namespace MWGui
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player))
return;
bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
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;
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), ""));

View file

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

View file

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

View file

@ -111,49 +111,6 @@ namespace MWGui
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)
{
mPtr = actor;
@ -162,10 +119,10 @@ namespace MWGui
mCurrentMerchantOffer = 0;
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);
// Important: actor goes last, so that items purchased by the merchant go into his inventory
itemSources.push_back(actor);
std::vector<MWWorld::Ptr> worldItems;
MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems);
@ -322,8 +279,8 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& playerBought = playerItemModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& merchantBought = mTradeModel->getItemsBorrowedToUs();
if (playerBought.empty() && merchantBought.empty())
{
// user notification
@ -354,7 +311,7 @@ namespace MWGui
}
// 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))
{
@ -421,8 +378,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up");
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
restock();
}
void TradeWindow::onAccept(MyGUI::EditBox *sender)
@ -536,7 +491,7 @@ namespace MWGui
// connected to buying and selling the same item.
// This value has been determined by researching the limitations of the vanilla formula
// 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)
{
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -545,7 +500,7 @@ namespace MWGui
merchantOffer -= std::max(cap, buyingPrice);
}
std::vector<ItemStack> merchantBorrowed = mTradeModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& merchantBorrowed = mTradeModel->getItemsBorrowedToUs();
for (const ItemStack& itemStack : merchantBorrowed)
{
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -590,4 +545,9 @@ namespace MWGui
mTradeModel = nullptr;
mSortModel = nullptr;
}
void TradeWindow::onClose()
{
resetReference();
}
}

View file

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

View file

@ -86,7 +86,7 @@ namespace MWGui
// Add price for the travelling 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
price *= 1 + static_cast<int>(followers.size());

View file

@ -963,6 +963,7 @@ namespace MWMechanics
{
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
bool wasDead = creatureStats.isDead();
@ -1014,8 +1015,11 @@ namespace MWMechanics
for (int i = 0; i < 3; ++i)
{
DynamicStat<float> stat = creatureStats.getDynamic(i);
stat.setCurrentModifier(effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude() -
effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(),
float fortify = effects.get(ESM::MagicEffect::FortifyHealth + 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
// Fatigue can be decreased below zero meaning the actor will be knocked out
i == 1 || i == 2);
@ -1027,9 +1031,14 @@ namespace MWMechanics
for(int i = 0;i < ESM::Attribute::Length;++i)
{
AttributeValue stat = creatureStats.getAttribute(i);
stat.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude()));
float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude();
float drain = 0.f, absorb = 0.f;
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);
}
@ -1278,14 +1287,20 @@ namespace MWMechanics
{
NpcStats &npcStats = ptr.getClass().getNpcStats(ptr);
const MagicEffects &effects = npcStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
// skills
for(int i = 0;i < ESM::Skill::Length;++i)
{
SkillValue& skill = npcStats.getSkill(i);
skill.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude()));
float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude();
float drain = 0.f, absorb = 0.f;
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()
{
const float minGap = 10.f;
const float maxDistToCheck = 100.f;
const float maxTimeToCheck = 1.f;
const float maxDistForPartialAvoiding = 200.f;
const float maxDistForStrictAvoiding = 100.f;
const float maxTimeToCheck = 2.0f;
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
MWWorld::Ptr player = getPlayer();
@ -1801,9 +1817,15 @@ namespace MWMechanics
if (ptr == player)
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);
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
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.
// 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)
continue;
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
osg::Vec2f baseSpeed = origMovement * maxSpeed;
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
float baseRotZ = ptr.getRefData().getPosition().rot[2];
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
float timeToCollision = maxTimeToCheck;
osg::Vec2f movementCorrection(0, 0);
@ -1851,9 +1873,10 @@ namespace MWMechanics
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
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.
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0)
if (dist > maxDistToCheck || relPos.y() < 0)
continue;
// Don't check for a collision if vertical distance is greater then the actor's height.
@ -1888,16 +1911,20 @@ namespace MWMechanics
timeToCollision = t;
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
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;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
movementCorrection.y() = std::max(0.f, movementCorrection.y());
if (otherPtr.getClass().getCreatureStats(otherPtr).isDead())
// 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)
{
// Try to evade the nearest collision.
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)
{ // Keep the original speed.
newMovement.normalize();
@ -1948,6 +1975,7 @@ namespace MWMechanics
if (!playerHitAttemptActor.isInCell())
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
}
bool godmode = MWBase::Environment::get().getWorld()->getGodModeState();
// AI and magic effects update
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
@ -2182,7 +2210,7 @@ namespace MWMechanics
iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor);
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();
// Handle player last, in case a cell transition occurs by casting a teleportation spell

View file

@ -308,8 +308,6 @@ namespace MWMechanics
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
if (storage.mReadyToAttack)
{
if (isRangedCombat)
{
// rotate actor taking into account target movement direction and projectile speed
@ -326,6 +324,8 @@ namespace MWMechanics
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
}
if (storage.mReadyToAttack)
{
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
// start new attack
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);

View file

@ -2087,7 +2087,7 @@ void CharacterController::update(float duration, bool animationOnly)
else if(!cls.getCreatureStats(mPtr).isDead())
{
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 flying = world->isFlying(mPtr);
bool solid = world->isActorCollisionEnabled(mPtr);

View file

@ -19,14 +19,14 @@ namespace MWMechanics
{
/// @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 MWWorld::Ptr& player = getPlayer();
int playerLevel = player.getClass().getCreatureStats(player).getLevel();
if (Misc::Rng::roll0to99() < levItem->mChanceNone)
if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone)
return std::string();
std::vector<std::string> candidates;
@ -55,7 +55,7 @@ namespace MWMechanics
}
if (candidates.empty())
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
if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item)))
@ -74,9 +74,9 @@ namespace MWMechanics
else
{
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
return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true);
return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true, seed);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -136,6 +136,10 @@ void ObjectList::addEntireContainer(const MWWorld::Ptr& 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)
{
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 (action == BaseObjectList::SET)
{
containerStore.setResolved(true);
containerStore.clear();
}
bool isLocalDrag = isLocalEvent && containerSubAction == BaseObjectList::DRAG;
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(),
ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum());
ptrFound.getClass().restock(ptrFound);
//ptrFound.getClass().restock(ptrFound);
reset();
packetOrigin = mwmp::PACKET_ORIGIN::CLIENT_GAMEPLAY;

View file

@ -25,37 +25,35 @@
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "mtphysics.hpp"
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)
, 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)
, mExternalCollisionMode(true)
, mCollisionWorld(world)
, mTaskScheduler(scheduler)
{
mPtr = ptr;
mHalfExtents = shape->mCollisionBoxHalfExtents;
mMeshTranslation = shape->mCollisionBoxTranslate;
// 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
// (NPCs have bodyparts and use a different approach)
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
{
const Resource::BulletShape* collisionShape = shape.get();
if (collisionShape && collisionShape->mCollisionShape)
if (shape->mCollisionShape)
{
btTransform transform;
transform.setIdentity();
btVector3 min;
btVector3 max;
collisionShape->mCollisionShape->getAabb(transform, min, max);
shape->mCollisionShape->getAabb(transform, min, max);
mHalfExtents.x() = (max[0] - min[0])/2.f;
mHalfExtents.y() = (max[1] - min[1])/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
*/
commitPositionChange();
}
Actor::~Actor()
{
if (mCollisionObject.get())
mCollisionWorld->removeCollisionObject(mCollisionObject.get());
if (mCollisionObject)
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
}
void Actor::enableCollisionMode(bool collision)
{
mInternalCollisionMode = collision;
mInternalCollisionMode.store(collision, std::memory_order_release);
}
void Actor::enableCollisionBody(bool collision)
@ -137,16 +137,15 @@ void Actor::enableCollisionBody(bool collision)
void Actor::addCollisionMask(int collisionMask)
{
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
}
void Actor::updateCollisionMask()
{
mCollisionWorld->removeCollisionObject(mCollisionObject.get());
addCollisionMask(getCollisionMask());
mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask());
}
int Actor::getCollisionMask()
int Actor::getCollisionMask() const
{
int collisionMask = CollisionType_World | CollisionType_HeightMap;
if (mExternalCollisionMode)
@ -154,58 +153,91 @@ int Actor::getCollisionMask()
if (mCanWaterWalk)
collisionMask |= CollisionType_Water;
return collisionMask;
}
void Actor::updatePosition()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
mPosition = position;
mPreviousPosition = position;
mTransformUpdatePending = true;
updateCollisionObjectPosition();
}
void Actor::updateCollisionObjectPosition()
{
btTransform tr = mCollisionObject->getWorldTransform();
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition;
tr.setOrigin(Misc::Convert::toBullet(newPosition));
mCollisionObject->setWorldTransform(tr);
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
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
{
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)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
else
{
mPreviousPosition = mPosition;
mPosition = position;
if (updateCollisionObject)
{
updateCollisionObjectPosition();
mCollisionObject->setWorldTransform(mLocalTransform);
}
}
}
osg::Vec3f Actor::getPosition() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return mPosition;
}
osg::Vec3f Actor::getPreviousPosition() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return mPreviousPosition;
}
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();
tr.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(tr);
mTransformUpdatePending = true;
updateCollisionObjectPosition();
}
@ -216,32 +248,37 @@ bool Actor::isRotationallyInvariant() const
void Actor::updateScale()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
float scale = mPtr.getCellRef().getScale();
osg::Vec3f scaleVec(scale,scale,scale);
mPtr.getClass().adjustScale(mPtr, scaleVec, false);
mScale = scaleVec;
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
mScaleUpdatePending = true;
scaleVec = osg::Vec3f(scale,scale,scale);
mPtr.getClass().adjustScale(mPtr, scaleVec, true);
mRenderingScale = scaleVec;
mTransformUpdatePending = true;
updateCollisionObjectPosition();
}
osg::Vec3f Actor::getHalfExtents() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mScale);
}
osg::Vec3f Actor::getOriginalHalfExtents() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return mHalfExtents;
}
osg::Vec3f Actor::getRenderingHalfExtents() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mRenderingScale);
}
@ -252,26 +289,27 @@ void Actor::setInertialForce(const osg::Vec3f &force)
void Actor::setOnGround(bool grounded)
{
mOnGround = grounded;
mOnGround.store(grounded, std::memory_order_release);
}
void Actor::setOnSlope(bool slope)
{
mOnSlope = slope;
mOnSlope.store(slope, std::memory_order_release);
}
bool Actor::isWalkingOnWater() const
{
return mWalkingOnWater;
return mWalkingOnWater.load(std::memory_order_acquire);
}
void Actor::setWalkingOnWater(bool walkingOnWater)
{
mWalkingOnWater = walkingOnWater;
mWalkingOnWater.store(walkingOnWater, std::memory_order_release);
}
void Actor::setCanWaterWalk(bool waterWalk)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (waterWalk != mCanWaterWalk)
{
mCanWaterWalk = waterWalk;

View file

@ -1,15 +1,17 @@
#ifndef OPENMW_MWPHYSICS_ACTOR_H
#define OPENMW_MWPHYSICS_ACTOR_H
#include <atomic>
#include <memory>
#include <mutex>
#include "ptrholder.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec3f>
#include <osg/Quat>
#include <osg/ref_ptr>
class btCollisionWorld;
class btCollisionShape;
class btCollisionObject;
class btConvexShape;
@ -21,12 +23,13 @@ namespace Resource
namespace MWPhysics
{
class PhysicsTaskScheduler;
class Actor : public PtrHolder
class Actor final : public PtrHolder
{
public:
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);
~Actor() override;
/**
* Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry.
@ -35,7 +38,7 @@ namespace MWPhysics
bool getCollisionMode() const
{
return mInternalCollisionMode;
return mInternalCollisionMode.load(std::memory_order_acquire);
}
btConvexShape* getConvexShape() const { return mConvexShape; }
@ -60,6 +63,7 @@ namespace MWPhysics
void updatePosition();
void updateCollisionObjectPosition();
void commitPositionChange();
/**
* 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.
* 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;
@ -110,14 +115,14 @@ namespace MWPhysics
bool getOnGround() const
{
return mInternalCollisionMode && mOnGround;
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire);
}
void setOnSlope(bool slope);
bool getOnSlope() const
{
return mInternalCollisionMode && mOnSlope;
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire);
}
btCollisionObject* getCollisionObject() const
@ -136,10 +141,10 @@ namespace MWPhysics
/// Removes then re-adds the collision object to the dynamics world
void updateCollisionMask();
void addCollisionMask(int collisionMask);
int getCollisionMask();
int getCollisionMask() const;
bool mCanWaterWalk;
bool mWalkingOnWater;
std::atomic<bool> mWalkingOnWater;
bool mRotationallyInvariant;
@ -156,14 +161,18 @@ namespace MWPhysics
osg::Vec3f mRenderingScale;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
btTransform mLocalTransform;
bool mScaleUpdatePending;
bool mTransformUpdatePending;
mutable std::mutex mPositionMutex;
osg::Vec3f mForce;
bool mOnGround;
bool mOnSlope;
bool mInternalCollisionMode;
std::atomic<bool> mOnGround;
std::atomic<bool> mOnSlope;
std::atomic<bool> mInternalCollisionMode;
bool mExternalCollisionMode;
btCollisionWorld* mCollisionWorld;
PhysicsTaskScheduler* mTaskScheduler;
Actor(const Actor&);
Actor& operator=(const Actor&);

View file

@ -10,18 +10,14 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/refdata.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "constants.hpp"
#include "physicssystem.hpp"
#include "stepper.hpp"
#include "trace.h"
@ -78,24 +74,26 @@ namespace MWPhysics
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,
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
WorldFrameData& worldData)
{
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
// (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr))
return position;
return;
// Reset per-frame data
physicActor->setWalkingOnWater(false);
// Anything to collide with?
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))
) * movement * time;
) * actor.mMovement * time;
return;
}
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
// other actors are using to collide against this actor.
// 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();
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
ActorTracer tracer;
osg::Vec3f inertia = physicActor->getInertialForce();
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
{
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())
|| (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
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;
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
{
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;
}
if (actor.mWantJump)
actor.mDidJump = true;
// 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())));
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
@ -170,7 +146,7 @@ namespace MWPhysics
Stepper stepper(collisionWorld, colobj);
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.
* 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;
// 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);
velocity = slide(velocity, down);
@ -235,7 +211,7 @@ namespace MWPhysics
if (result)
{
// 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;
}
else
@ -245,7 +221,7 @@ namespace MWPhysics
// Do not allow sliding upward if there is gravity.
// 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);
if ((newVelocity-velocity).length2() < 0.01)
@ -269,11 +245,11 @@ namespace MWPhysics
const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder)
standingCollisionTracker[ptr] = ptrHolder->getPtr();
actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true);
if (!isFlying)
if (!actor.mFlying)
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
isOnGround = true;
@ -292,7 +268,7 @@ namespace MWPhysics
btVector3 aabbMin, aabbMax;
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
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 *= 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));
else
{
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
if (inertia.z() < 0)
inertia.z() *= slowFall;
if (slowFall < 1.f) {
inertia.x() *= slowFall;
inertia.y() *= slowFall;
inertia.z() *= actor.mSlowFall;
if (actor.mSlowFall < 1.f) {
inertia.x() *= actor.mSlowFall;
inertia.y() *= actor.mSlowFall;
}
physicActor->setInertialForce(inertia);
}
@ -319,6 +295,6 @@ namespace MWPhysics
physicActor->setOnSlope(isOnSlope);
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
return newPosition;
actor.mPosition = newPosition;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#include <array>
#include <memory>
#include <map>
#include <set>
@ -47,11 +48,57 @@ class btCollisionShape;
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 Object;
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
{
@ -93,7 +140,7 @@ namespace MWPhysics
bool toggleCollisionMode();
void stepSimulation(float dt);
void stepSimulation();
void debugDraw();
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,
const osg::Vec3f &origin,
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
@ -146,7 +193,7 @@ namespace MWPhysics
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// 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.
void clearQueuedMovement();
@ -200,39 +247,41 @@ namespace MWPhysics
void updateWater();
std::vector<ActorFrameData> prepareFrameData();
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
btBroadphaseInterface* mBroadphase;
btDefaultCollisionConfiguration* mCollisionConfiguration;
btCollisionDispatcher* mDispatcher;
btCollisionWorld* mCollisionWorld;
std::unique_ptr<btBroadphaseInterface> mBroadphase;
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
std::unique_ptr<btCollisionDispatcher> mDispatcher;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
Resource::ResourceSystem* mResourceSystem;
typedef std::map<MWWorld::ConstPtr, Object*> ObjectMap;
using ObjectMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Object>>;
ObjectMap 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;
typedef std::map<std::pair<int, int>, HeightField*> HeightFieldMap;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields;
bool mDebugDrawEnabled;
// 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.
typedef std::map<MWWorld::Ptr, MWWorld::Ptr> CollisionMap;
CollisionMap mStandingCollisions;
// 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);
using PtrVelocityList = std::vector<std::pair<MWWorld::Ptr, osg::Vec3f>>;
PtrVelocityList mMovementQueue;
PtrVelocityList mMovementResults;
float mTimeAccum;

View file

@ -252,11 +252,15 @@ osg::Group *CreatureWeaponAnimation::getArrowBone()
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
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);
if (bone == nullptr)
{
SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
mWeapon->getNode()->accept(findVisitor);
return findVisitor.mFoundNode;
bone = findVisitor.mFoundNode;
}
return bone;
}
osg::Node *CreatureWeaponAnimation::getWeaponNode()

View file

@ -1078,10 +1078,15 @@ osg::Group* NpcAnimation::getArrowBone()
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
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);
if (bone == nullptr)
{
SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
part->getNode()->accept(findVisitor);
return findVisitor.mFoundNode;
bone = findVisitor.mFoundNode;
}
return bone;
}
osg::Node* NpcAnimation::getWeaponNode()

View file

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

View file

@ -40,7 +40,7 @@ namespace MWWorld
{
// Find any NPCs that are following the actor and teleport them with him
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)
teleport(*it);
@ -82,7 +82,9 @@ namespace MWWorld
MWWorld::CellStore *newCellStore;
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 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;
MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers);
@ -159,11 +161,17 @@ namespace MWWorld
MWWorld::Ptr follower = *it;
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)
continue;
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800)
out.insert(follower);
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800)
continue;
out.emplace(follower);
}
}
}

View file

@ -28,8 +28,9 @@ namespace MWWorld
/// @param teleportFollowers Whether to teleport any following actors of the target actor as well.
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
static void getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
/// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output,
/// e.g. so that the teleport action can calm them.
static void getFollowers(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out, bool includeHostiles = false);
};
}

View file

@ -1247,7 +1247,8 @@ namespace MWWorld
for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
{
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);
}

View file

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

View file

@ -12,6 +12,7 @@
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include <components/openmw-mp/TimedLog.hpp>
/*
End of tes3mp addition
*/
@ -35,6 +36,21 @@
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>
float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList)
{
@ -56,6 +72,7 @@ namespace
MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id,
MWWorld::ContainerStore *store)
{
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id);
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>
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection,
const ESM::ObjectState& state)
@ -131,7 +160,11 @@ MWWorld::ContainerStore::ContainerStore()
: mListener(nullptr)
, mRechargingItemsUpToDate(false)
, mCachedWeight (0)
, mWeightUpToDate (false) {}
, mWeightUpToDate (false)
, mModified(false)
, mResolved(false)
, mSeed()
, mPtr() {}
MWWorld::ContainerStore::~ContainerStore() {}
@ -165,22 +198,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
return ContainerStoreIterator (this);
}
int MWWorld::ContainerStore::count(const std::string &id)
int MWWorld::ContainerStore::count(const std::string &id) const
{
int total=0;
for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
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();
for (const auto& iter : *this)
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
total += iter.getRefData().getCount();
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)
{
resolve();
if (ptr.getRefData().getCount() <= count)
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
@ -230,6 +254,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr,
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item)
{
resolve();
MWWorld::ContainerStoreIterator retval = end();
for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
{
@ -247,7 +272,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::
{
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);
retval = iter;
break;
@ -391,8 +416,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
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);
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))
{
iter->getRefData().setCount(iter->getRefData().getCount() + realCount);
iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount));
flagAsModified();
return iter;
}
@ -424,7 +451,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr,
if (stacks(*iter, ptr))
{
// stack
iter->getRefData().setCount( iter->getRefData().getCount() + count );
iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
flagAsModified();
return iter;
@ -502,6 +529,7 @@ void MWWorld::ContainerStore::updateRechargingItems()
int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor)
{
resolve();
int toRemove = count;
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)
{
assert(this == item.getContainerStore());
resolve();
/*
Start of tes3mp addition
@ -557,7 +586,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
}
else
{
itemRef.setCount(itemRef.getCount() - toRemove);
itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove));
toRemove = 0;
}
@ -580,20 +609,33 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
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();
++iter)
for (const ESM::ContItem& iter : items.mList)
{
std::string id = Misc::StringUtils::lowerCase(iter->mItem);
addInitialItem(id, owner, iter->mCount);
std::string id = Misc::StringUtils::lowerCase(iter.mItem);
addInitialItem(id, owner, iter.mCount, &seed);
}
flagAsModified();
mResolved = true;
}
void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner,
int count, bool topLevel, const std::string& levItem)
void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed)
{
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.
try
@ -601,13 +643,13 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
if (ref.getPtr().getClass().getScript(ref.getPtr()).empty())
{
addInitialItemImp(ref.getPtr(), owner, count, topLevel, levItem);
addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel, levItem);
}
else
{
// 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++)
addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, topLevel, levItem);
for (int i = 0; i < std::abs(count); i++)
addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel, levItem);
}
}
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,
int count, bool topLevel, const std::string& levItem)
void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count,
Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem)
{
if (ptr.getTypeName()==typeid (ESM::ItemLevList).name())
{
if(!seed)
return;
const ESM::ItemLevList* levItemList = ptr.get<ESM::ItemLevList>()->mBase;
if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each)
{
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;
}
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())
return;
addInitialItem(itemId, owner, count, false, levItemList->mId);
addInitialItem(itemId, owner, count, seed, false, levItemList->mId);
}
}
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);
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()
{
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
iter->getRefData().setCount (0);
flagAsModified();
mModified = true;
}
void MWWorld::ContainerStore::flagAsModified()
@ -755,6 +703,59 @@ void MWWorld::ContainerStore::flagAsModified()
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
{
if (!mWeightUpToDate)
@ -851,6 +852,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
{
resolve();
{
Ptr ptr = searchId (potions, id, this);
if (!ptr.isEmpty())
@ -926,6 +928,22 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
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
{
state.mItems.clear();
@ -943,13 +961,13 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
storeStates (repairs, state, index);
storeStates (weapons, state, index, true);
storeStates (lights, state, index, true);
state.mLevelledItemMap = mLevelledItemMap;
}
void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
{
clear();
mModified = true;
mResolved = true;
int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator
@ -983,9 +1001,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
break;
}
}
mLevelledItemMap = inventory.mLevelledItemMap;
}
template<class PtrType>

View file

@ -3,6 +3,7 @@
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <components/esm/loadalch.hpp>
@ -18,6 +19,8 @@
#include <components/esm/loadrepa.hpp>
#include <components/esm/loadweap.hpp>
#include <components/misc/rng.hpp>
#include "ptr.hpp"
#include "cellreflist.hpp"
@ -27,6 +30,11 @@ namespace ESM
struct InventoryState;
}
namespace MWClass
{
class Container;
}
namespace MWWorld
{
class ContainerStore;
@ -37,6 +45,21 @@ namespace MWWorld
typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator;
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
{
@ -93,15 +116,18 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Repair> repairs;
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 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 = "");
void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = "");
bool mModified;
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>
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.
/// @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?
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;
void setContListener(ContainerStoreListener* listener);
protected:
ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count);
///< Add the item to this container (do not try to stack it onto existing items)
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:
virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
///< @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.
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();
///< Empty container.
@ -220,8 +246,26 @@ namespace MWWorld
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<ConstPtr>;
friend class ResolutionListener;
friend class MWClass::Container;
};

View file

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

View file

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

View file

@ -242,7 +242,8 @@ namespace MWWorld
MWWorld::Ptr player = getPlayer();
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;
MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject();

View file

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

View file

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

View file

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

View file

@ -167,7 +167,7 @@ namespace MWWorld
const std::string& resourcePath, const std::string& userDataPath)
: mResourceSystem(resourceSystem), mLocalScripts (mStore),
mCells (mStore, mEsm), mSky (true),
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles),
mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
mUserDataPath(userDataPath), mShouldUpdateNavigator(false),
mActivationDistanceOverride (activationDistanceOverride),
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)
{
mPhysics->clearQueuedMovement();
mDiscardMovements = true;
if (changeEvent && mCurrentWorldSpace != cellName)
{
@ -1069,6 +1070,7 @@ namespace MWWorld
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{
mPhysics->clearQueuedMovement();
mDiscardMovements = true;
if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
{
@ -1688,23 +1690,22 @@ namespace MWWorld
void World::doPhysics(float duration)
{
mPhysics->stepSimulation(duration);
mPhysics->stepSimulation();
processDoors(duration);
mProjectileManager->update(duration);
const MWPhysics::PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
MWPhysics::PtrVelocityList::const_iterator player(results.end());
for(MWPhysics::PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter)
{
if(iter->first == getPlayerPtr())
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements);
mDiscardMovements = false;
for(const auto& result : results)
{
// Handle player last, in case a cell transition occurs
player = iter;
continue;
}
moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false);
if(result.first != getPlayerPtr())
moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false);
}
const auto player = results.find(getPlayerPtr());
if (player != results.end())
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
}
@ -1733,7 +1734,7 @@ namespace MWWorld
*object->getShapeInstance()->getCollisionShape(),
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
@ -2062,10 +2063,13 @@ namespace MWWorld
else
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)));
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)));
}
@ -4350,7 +4354,7 @@ namespace MWWorld
btVector3 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 localTo = toLocal(Misc::Convert::toBullet(destination));

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ namespace Compiler
{
public:
virtual const char *what() const throw() { return "Compile error";}
const char *what() const noexcept override { return "Compile error";}
///< Return error message
};
@ -21,7 +21,7 @@ namespace Compiler
{
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
};
@ -31,7 +31,7 @@ namespace Compiler
{
public:
virtual const char *what() const throw() { return "End of file"; }
const char *what() const noexcept final { return "End of file"; }
///< Return error message
};
}

View file

@ -38,8 +38,14 @@ namespace DetourNavigator
return stream << "failed";
case UpdateNavMeshStatus::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,
@ -126,7 +132,7 @@ namespace DetourNavigator
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();
while (!mShouldStop)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,6 +11,8 @@
#include <list>
#include <mutex>
#include <cassert>
#include <cstring>
#include <vector>
namespace osg
{
@ -33,10 +35,10 @@ namespace DetourNavigator
std::atomic<std::int64_t> mUseCount;
osg::Vec3f mAgentHalfExtents;
TilePosition mChangedTile;
std::string mNavMeshKey;
std::vector<unsigned char> mNavMeshKey;
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)
, mAgentHalfExtents(agentHalfExtents)
, mChangedTile(changedTile)
@ -120,19 +122,32 @@ namespace DetourNavigator
virtual ~KeyView() = default;
KeyView(const std::string& value)
KeyView(const std::vector<unsigned char>& value)
: mValue(&value) {}
const std::string& getValue() const
const std::vector<unsigned char>& getValue() const
{
assert(mValue);
return *mValue;
}
virtual int compare(const std::string& other) const
virtual int compare(const std::vector<unsigned char>& other) const
{
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
@ -147,7 +162,7 @@ namespace DetourNavigator
}
private:
const std::string* mValue = nullptr;
const std::vector<unsigned char>* mValue = nullptr;
};
class RecastMeshKeyView : public KeyView
@ -156,7 +171,7 @@ namespace DetourNavigator
RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& 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
{

View file

@ -10,22 +10,22 @@ namespace DetourNavigator
{
public:
template <class T>
explicit ObjectId(T* value) throw()
explicit ObjectId(T* value) noexcept
: mValue(reinterpret_cast<std::size_t>(value))
{
}
std::size_t value() const throw()
std::size_t value() const noexcept
{
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;
}
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;
}
@ -40,7 +40,7 @@ namespace std
template <>
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();
}

View file

@ -6,7 +6,6 @@
#include <vector>
#include <stdint.h>
#include <string.h>
namespace ESM
{
@ -57,11 +56,16 @@ public:
}
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); }
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(""); }
private:
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; }
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* 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<32> NAME32;
typedef FIXED_STRING<64> NAME64;
typedef FIXED_STRING<256> NAME256;
/* This struct defines a file 'context' which can be saved and later
restored by an ESMReader instance. It will save the position within

View file

@ -3,6 +3,8 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/misc/stringops.hpp>
void ESM::InventoryState::load (ESMReader &esm)
{
// obsolete
@ -106,6 +108,19 @@ void ESM::InventoryState::load (ESMReader &esm)
mSelectedEnchantItem = -1;
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

View file

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

View file

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

View file

@ -3,29 +3,44 @@
#include <chrono>
#include <random>
namespace
{
Misc::Rng::Seed sSeed;
}
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)
{
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()

View file

@ -13,24 +13,32 @@ namespace Misc
class Rng
{
public:
class Seed
{
std::mt19937 mGenerator;
public:
Seed();
Seed(const Seed&) = delete;
Seed(unsigned int seed);
friend class Rng;
};
/// create a RNG
static std::mt19937 generator;
static Seed& getSeed();
/// seed the RNG
static void init(unsigned int seed = generateDefaultSeed());
/// 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.
static float rollClosedProbability();
static float rollClosedProbability(Seed& seed = getSeed());
/// 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]
static int roll0to99() { return rollDice(100); }
static int roll0to99(Seed& seed = getSeed()) { return rollDice(100, seed); }
/// returns default seed for RNG
static unsigned int generateDefaultSeed();

View file

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

View file

@ -14,16 +14,31 @@ namespace Nif
if (external)
filename = nif->getString();
else
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3))
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);
}
pixel = nif->getUInt();
mipmap = nif->getUInt();
alpha = nif->getUInt();
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)
@ -79,6 +94,12 @@ namespace Nif
NiParticleModifier::read(nif);
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)

View file

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

View file

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

View file

@ -33,13 +33,33 @@ void NiSkinInstance::post(NIFFile *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();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
nif->skip(2); // Keep flags and compress flags
if (nif->getBoolean())
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())
{
nif->getVector3s(normals, verts);
if (dataFlags & 0x1000)
{
nif->getVector3s(tangents, verts);
nif->getVector3s(bitangents, verts);
}
}
center = nif->getVector3();
radius = nif->getFloat();
@ -47,14 +67,27 @@ void NiGeometryData::read(NIFStream *nif)
if (nif->getBoolean())
nif->getVector4s(colors, verts);
// Only the first 6 bits are used as a count. I think the rest are
// flags of some sort.
unsigned int numUVs = dataFlags;
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
{
numUVs = nif->getUShort();
// In Morrowind this field only corresponds to the number of UV sets.
// NifTools research is inaccurate.
int uvs = nif->getUShort();
if (nif->getVersion() > NIFFile::NIFVersion::VER_MW)
numUVs &= 0x3f;
}
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0)
numUVs &= 0x1;
if(nif->getInt())
bool hasUVs = true;
if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW)
hasUVs = nif->getBoolean();
if (hasUVs)
{
uvlist.resize(uvs);
for(int i = 0;i < uvs;i++)
uvlist.resize(numUVs);
for (unsigned int i = 0; i < numUVs; i++)
{
nif->getVector2s(uvlist[i], verts);
// 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)
@ -75,13 +114,17 @@ void NiTriShapeData::read(NIFStream *nif)
// We have three times as many vertices as triangles, so this
// is always equal to tris*3.
int cnt = nif->getInt();
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
// vertices. We don't actually need need this for anything, so
// just skip it.
int verts = nif->getUShort();
for(int i=0;i < verts;i++)
unsigned short verts = nif->getUShort();
for (unsigned short i=0; i < verts; i++)
{
// Number of vertices matching vertex 'i'
int num = nif->getUShort();
@ -101,7 +144,11 @@ void NiTriStripsData::read(NIFStream *nif)
std::vector<unsigned short> lengths;
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;
strips.resize(numStrips);
@ -140,28 +187,38 @@ void NiAutoNormalParticlesData::read(NIFStream *nif)
NiGeometryData::read(nif);
// Should always match the number of vertices
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();
if (nif->getBoolean())
{
// Particle sizes
if (nif->getBoolean())
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)
{
NiAutoNormalParticlesData::read(nif);
if (nif->getBoolean())
{
// Rotation quaternions.
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean())
nif->getQuaternions(rotations, vertices.size());
}
}
void NiPosData::read(NIFStream *nif)
{
@ -188,12 +245,27 @@ void NiPixelData::read(NIFStream *nif)
{
fmt = (Format)nif->getUInt();
if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
{
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);
numberOfMipmaps = nif->getUInt();
@ -213,8 +285,10 @@ void NiPixelData::read(NIFStream *nif)
// Read the data
unsigned int numPixels = nif->getUInt();
if (numPixels)
nif->getUChars(data, numPixels);
bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10,4,0,2);
unsigned int numFaces = hasFaces ? nif->getUInt() : 1;
if (numPixels && numFaces)
nif->getUChars(data, numPixels * numFaces);
}
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))
nif->skip(4); // NiSkinPartition link
// Has vertex weights flag
if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean())
return;
bones.resize(boneNum);
for (BoneInfo &bi : bones)
{
@ -272,7 +350,7 @@ void NiMorphData::read(NIFStream *nif)
{
int morphCount = nif->getInt();
int vertCount = nif->getInt();
/*relative targets?*/nif->getChar();
nif->getChar(); // Relative targets, always 1
mMorphs.resize(morphCount);
for(int i = 0;i < morphCount;i++)
@ -290,6 +368,7 @@ void NiKeyframeData::read(NIFStream *nif)
if(mRotations->mInterpolationType == InterpolationType_XYZ)
{
//Chomp unused float
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->getFloat();
mXRotations = std::make_shared<FloatKeyMap>();
mYRotations = std::make_shared<FloatKeyMap>();

View file

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

View file

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

View file

@ -34,6 +34,9 @@ struct NiDynamicEffect : public Node
void read(NIFStream *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();
for (unsigned int i=0; i<numAffectedNodes; ++i)
nif->getUInt(); // ref to another Node

View file

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

View file

@ -60,5 +60,54 @@ public:
void read(NIFStream *nif);
};
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
#endif

View file

@ -16,108 +16,106 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
NIFFile::~NIFFile()
{
for (std::vector<Record*>::iterator it = records.begin() ; it != records.end(); ++it)
{
delete *it;
}
for (Record* record : records)
delete record;
}
template <typename NodeType> static Record* construct() { return new NodeType; }
struct RecordFactoryEntry {
typedef Record* (*create_t) ();
using create_t = Record* (*)();
create_t mCreate;
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.
static std::map<std::string,RecordFactoryEntry> makeFactory()
{
std::map<std::string,RecordFactoryEntry> newFactory;
newFactory.insert(makeEntry("NiNode", &construct <NiNode> , RC_NiNode ));
newFactory.insert(makeEntry("NiSwitchNode", &construct <NiSwitchNode> , RC_NiSwitchNode ));
newFactory.insert(makeEntry("NiLODNode", &construct <NiLODNode> , RC_NiLODNode ));
newFactory.insert(makeEntry("AvoidNode", &construct <NiNode> , RC_AvoidNode ));
newFactory.insert(makeEntry("NiCollisionSwitch", &construct <NiNode> , RC_NiCollisionSwitch ));
newFactory.insert(makeEntry("NiBSParticleNode", &construct <NiNode> , RC_NiBSParticleNode ));
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode ));
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode ));
newFactory.insert(makeEntry("NiTriShape", &construct <NiTriShape> , RC_NiTriShape ));
newFactory.insert(makeEntry("NiTriStrips", &construct <NiTriStrips> , RC_NiTriStrips ));
newFactory.insert(makeEntry("NiLines", &construct <NiLines> , RC_NiLines ));
newFactory.insert(makeEntry("NiRotatingParticles", &construct <NiRotatingParticles> , RC_NiRotatingParticles ));
newFactory.insert(makeEntry("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles ));
newFactory.insert(makeEntry("NiCamera", &construct <NiCamera> , RC_NiCamera ));
newFactory.insert(makeEntry("RootCollisionNode", &construct <NiNode> , RC_RootCollisionNode ));
newFactory.insert(makeEntry("NiTexturingProperty", &construct <NiTexturingProperty> , RC_NiTexturingProperty ));
newFactory.insert(makeEntry("NiFogProperty", &construct <NiFogProperty> , RC_NiFogProperty ));
newFactory.insert(makeEntry("NiMaterialProperty", &construct <NiMaterialProperty> , RC_NiMaterialProperty ));
newFactory.insert(makeEntry("NiZBufferProperty", &construct <NiZBufferProperty> , RC_NiZBufferProperty ));
newFactory.insert(makeEntry("NiAlphaProperty", &construct <NiAlphaProperty> , RC_NiAlphaProperty ));
newFactory.insert(makeEntry("NiVertexColorProperty", &construct <NiVertexColorProperty> , RC_NiVertexColorProperty ));
newFactory.insert(makeEntry("NiShadeProperty", &construct <NiShadeProperty> , RC_NiShadeProperty ));
newFactory.insert(makeEntry("NiDitherProperty", &construct <NiDitherProperty> , RC_NiDitherProperty ));
newFactory.insert(makeEntry("NiWireframeProperty", &construct <NiWireframeProperty> , RC_NiWireframeProperty ));
newFactory.insert(makeEntry("NiSpecularProperty", &construct <NiSpecularProperty> , RC_NiSpecularProperty ));
newFactory.insert(makeEntry("NiStencilProperty", &construct <NiStencilProperty> , RC_NiStencilProperty ));
newFactory.insert(makeEntry("NiVisController", &construct <NiVisController> , RC_NiVisController ));
newFactory.insert(makeEntry("NiGeomMorpherController", &construct <NiGeomMorpherController> , RC_NiGeomMorpherController ));
newFactory.insert(makeEntry("NiKeyframeController", &construct <NiKeyframeController> , RC_NiKeyframeController ));
newFactory.insert(makeEntry("NiAlphaController", &construct <NiAlphaController> , RC_NiAlphaController ));
newFactory.insert(makeEntry("NiRollController", &construct <NiRollController> , RC_NiRollController ));
newFactory.insert(makeEntry("NiUVController", &construct <NiUVController> , RC_NiUVController ));
newFactory.insert(makeEntry("NiPathController", &construct <NiPathController> , RC_NiPathController ));
newFactory.insert(makeEntry("NiMaterialColorController", &construct <NiMaterialColorController> , RC_NiMaterialColorController ));
newFactory.insert(makeEntry("NiBSPArrayController", &construct <NiBSPArrayController> , RC_NiBSPArrayController ));
newFactory.insert(makeEntry("NiParticleSystemController", &construct <NiParticleSystemController> , RC_NiParticleSystemController ));
newFactory.insert(makeEntry("NiFlipController", &construct <NiFlipController> , RC_NiFlipController ));
newFactory.insert(makeEntry("NiAmbientLight", &construct <NiLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiDirectionalLight", &construct <NiLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiPointLight", &construct <NiPointLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiSpotLight", &construct <NiSpotLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiTextureEffect", &construct <NiTextureEffect> , RC_NiTextureEffect ));
newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData ));
newFactory.insert(makeEntry("NiTextKeyExtraData", &construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData ));
newFactory.insert(makeEntry("NiStringExtraData", &construct <NiStringExtraData> , RC_NiStringExtraData ));
newFactory.insert(makeEntry("NiGravity", &construct <NiGravity> , RC_NiGravity ));
newFactory.insert(makeEntry("NiPlanarCollider", &construct <NiPlanarCollider> , RC_NiPlanarCollider ));
newFactory.insert(makeEntry("NiSphericalCollider", &construct <NiSphericalCollider> , RC_NiSphericalCollider ));
newFactory.insert(makeEntry("NiParticleGrowFade", &construct <NiParticleGrowFade> , RC_NiParticleGrowFade ));
newFactory.insert(makeEntry("NiParticleColorModifier", &construct <NiParticleColorModifier> , RC_NiParticleColorModifier ));
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation ));
newFactory.insert(makeEntry("NiFloatData", &construct <NiFloatData> , RC_NiFloatData ));
newFactory.insert(makeEntry("NiTriShapeData", &construct <NiTriShapeData> , RC_NiTriShapeData ));
newFactory.insert(makeEntry("NiTriStripsData", &construct <NiTriStripsData> , RC_NiTriStripsData ));
newFactory.insert(makeEntry("NiLinesData", &construct <NiLinesData> , RC_NiLinesData ));
newFactory.insert(makeEntry("NiVisData", &construct <NiVisData> , RC_NiVisData ));
newFactory.insert(makeEntry("NiColorData", &construct <NiColorData> , RC_NiColorData ));
newFactory.insert(makeEntry("NiPixelData", &construct <NiPixelData> , RC_NiPixelData ));
newFactory.insert(makeEntry("NiMorphData", &construct <NiMorphData> , RC_NiMorphData ));
newFactory.insert(makeEntry("NiKeyframeData", &construct <NiKeyframeData> , RC_NiKeyframeData ));
newFactory.insert(makeEntry("NiSkinData", &construct <NiSkinData> , RC_NiSkinData ));
newFactory.insert(makeEntry("NiUVData", &construct <NiUVData> , RC_NiUVData ));
newFactory.insert(makeEntry("NiPosData", &construct <NiPosData> , RC_NiPosData ));
newFactory.insert(makeEntry("NiRotatingParticlesData", &construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData ));
newFactory.insert(makeEntry("NiAutoNormalParticlesData", &construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData ));
newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper ));
newFactory.insert(makeEntry("NiSourceTexture", &construct <NiSourceTexture> , RC_NiSourceTexture ));
newFactory.insert(makeEntry("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance ));
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController ));
newFactory.insert(makeEntry("NiPalette", &construct <NiPalette> , RC_NiPalette ));
return newFactory;
std::map<std::string,RecordFactoryEntry> factory;
factory["NiNode"] = {&construct <NiNode> , RC_NiNode };
factory["NiSwitchNode"] = {&construct <NiSwitchNode> , RC_NiSwitchNode };
factory["NiLODNode"] = {&construct <NiLODNode> , RC_NiLODNode };
factory["AvoidNode"] = {&construct <NiNode> , RC_AvoidNode };
factory["NiCollisionSwitch"] = {&construct <NiNode> , RC_NiCollisionSwitch };
factory["NiBSParticleNode"] = {&construct <NiNode> , RC_NiBSParticleNode };
factory["NiBSAnimationNode"] = {&construct <NiNode> , RC_NiBSAnimationNode };
factory["NiBillboardNode"] = {&construct <NiNode> , RC_NiBillboardNode };
factory["NiTriShape"] = {&construct <NiTriShape> , RC_NiTriShape };
factory["NiTriStrips"] = {&construct <NiTriStrips> , RC_NiTriStrips };
factory["NiLines"] = {&construct <NiLines> , RC_NiLines };
factory["NiRotatingParticles"] = {&construct <NiRotatingParticles> , RC_NiRotatingParticles };
factory["NiAutoNormalParticles"] = {&construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles };
factory["NiCamera"] = {&construct <NiCamera> , RC_NiCamera };
factory["RootCollisionNode"] = {&construct <NiNode> , RC_RootCollisionNode };
factory["NiTexturingProperty"] = {&construct <NiTexturingProperty> , RC_NiTexturingProperty };
factory["NiFogProperty"] = {&construct <NiFogProperty> , RC_NiFogProperty };
factory["NiMaterialProperty"] = {&construct <NiMaterialProperty> , RC_NiMaterialProperty };
factory["NiZBufferProperty"] = {&construct <NiZBufferProperty> , RC_NiZBufferProperty };
factory["NiAlphaProperty"] = {&construct <NiAlphaProperty> , RC_NiAlphaProperty };
factory["NiVertexColorProperty"] = {&construct <NiVertexColorProperty> , RC_NiVertexColorProperty };
factory["NiShadeProperty"] = {&construct <NiShadeProperty> , RC_NiShadeProperty };
factory["NiDitherProperty"] = {&construct <NiDitherProperty> , RC_NiDitherProperty };
factory["NiWireframeProperty"] = {&construct <NiWireframeProperty> , RC_NiWireframeProperty };
factory["NiSpecularProperty"] = {&construct <NiSpecularProperty> , RC_NiSpecularProperty };
factory["NiStencilProperty"] = {&construct <NiStencilProperty> , RC_NiStencilProperty };
factory["NiVisController"] = {&construct <NiVisController> , RC_NiVisController };
factory["NiGeomMorpherController"] = {&construct <NiGeomMorpherController> , RC_NiGeomMorpherController };
factory["NiKeyframeController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
factory["NiAlphaController"] = {&construct <NiAlphaController> , RC_NiAlphaController };
factory["NiRollController"] = {&construct <NiRollController> , RC_NiRollController };
factory["NiUVController"] = {&construct <NiUVController> , RC_NiUVController };
factory["NiPathController"] = {&construct <NiPathController> , RC_NiPathController };
factory["NiMaterialColorController"] = {&construct <NiMaterialColorController> , RC_NiMaterialColorController };
factory["NiBSPArrayController"] = {&construct <NiBSPArrayController> , RC_NiBSPArrayController };
factory["NiParticleSystemController"] = {&construct <NiParticleSystemController> , RC_NiParticleSystemController };
factory["NiFlipController"] = {&construct <NiFlipController> , RC_NiFlipController };
factory["NiAmbientLight"] = {&construct <NiLight> , RC_NiLight };
factory["NiDirectionalLight"] = {&construct <NiLight> , RC_NiLight };
factory["NiPointLight"] = {&construct <NiPointLight> , RC_NiLight };
factory["NiSpotLight"] = {&construct <NiSpotLight> , RC_NiLight };
factory["NiTextureEffect"] = {&construct <NiTextureEffect> , RC_NiTextureEffect };
factory["NiVertWeightsExtraData"] = {&construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData };
factory["NiTextKeyExtraData"] = {&construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData };
factory["NiStringExtraData"] = {&construct <NiStringExtraData> , RC_NiStringExtraData };
factory["NiGravity"] = {&construct <NiGravity> , RC_NiGravity };
factory["NiPlanarCollider"] = {&construct <NiPlanarCollider> , RC_NiPlanarCollider };
factory["NiSphericalCollider"] = {&construct <NiSphericalCollider> , RC_NiSphericalCollider };
factory["NiParticleGrowFade"] = {&construct <NiParticleGrowFade> , RC_NiParticleGrowFade };
factory["NiParticleColorModifier"] = {&construct <NiParticleColorModifier> , RC_NiParticleColorModifier };
factory["NiParticleRotation"] = {&construct <NiParticleRotation> , RC_NiParticleRotation };
factory["NiFloatData"] = {&construct <NiFloatData> , RC_NiFloatData };
factory["NiTriShapeData"] = {&construct <NiTriShapeData> , RC_NiTriShapeData };
factory["NiTriStripsData"] = {&construct <NiTriStripsData> , RC_NiTriStripsData };
factory["NiLinesData"] = {&construct <NiLinesData> , RC_NiLinesData };
factory["NiVisData"] = {&construct <NiVisData> , RC_NiVisData };
factory["NiColorData"] = {&construct <NiColorData> , RC_NiColorData };
factory["NiPixelData"] = {&construct <NiPixelData> , RC_NiPixelData };
factory["NiMorphData"] = {&construct <NiMorphData> , RC_NiMorphData };
factory["NiKeyframeData"] = {&construct <NiKeyframeData> , RC_NiKeyframeData };
factory["NiSkinData"] = {&construct <NiSkinData> , RC_NiSkinData };
factory["NiUVData"] = {&construct <NiUVData> , RC_NiUVData };
factory["NiPosData"] = {&construct <NiPosData> , RC_NiPosData };
factory["NiRotatingParticlesData"] = {&construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData };
factory["NiAutoNormalParticlesData"] = {&construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData };
factory["NiSequenceStreamHelper"] = {&construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper };
factory["NiSourceTexture"] = {&construct <NiSourceTexture> , RC_NiSourceTexture };
factory["NiSkinInstance"] = {&construct <NiSkinInstance> , RC_NiSkinInstance };
factory["NiLookAtController"] = {&construct <NiLookAtController> , RC_NiLookAtController };
factory["NiPalette"] = {&construct <NiPalette> , RC_NiPalette };
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
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.
if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW)
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
size_t recNum = nif.getInt();
size_t recNum = nif.getUInt();
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++)
{
Record *r = nullptr;
std::string rec = nif.getString();
std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString();
if(rec.empty())
{
std::stringstream error;
@ -164,6 +224,17 @@ void NIFFile::parse(Files::IStreamPtr stream)
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);
if (entry != factories.end())
@ -201,8 +272,8 @@ void NIFFile::parse(Files::IStreamPtr stream)
}
// Once parsing is done, do post-processing.
for(size_t i=0; i<recNum; i++)
records[i]->post(this);
for (Record* record : records)
record->post(this);
}
void NIFFile::setUseSkinning(bool skinning)

View file

@ -25,18 +25,19 @@ namespace Nif
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()
{
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()
{
return getSizedString();
return getVersion() < generateVersion(20,1,0,1) ? getSizedString() : file->getString(getUInt());
}
// Convenience utility functions: get the versions of the currently read file
unsigned int NIFStream::getVersion() const { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); }

View file

@ -30,7 +30,7 @@ public:
PropertyList props;
// Bounding box info
bool hasBounds;
bool hasBounds{false};
osg::Vec3f boundPos;
Matrix3 boundRot;
osg::Vec3f boundXYZ; // Box size
@ -39,11 +39,14 @@ public:
{
Named::read(nif);
flags = nif->getUShort();
flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt();
trafo = nif->getTrafo();
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
velocity = nif->getVector3();
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
props.read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
hasBounds = nif->getBoolean();
if(hasBounds)
{
@ -52,6 +55,9 @@ public:
boundRot = nif->getMatrix3();
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;
@ -102,6 +108,7 @@ struct NiNode : Node
{
Node::read(nif);
children.read(nif);
if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
effects.read(nif);
// Discard transformations for the root node, otherwise some meshes
@ -130,7 +137,39 @@ struct NiNode : 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;
MaterialData materialData;
};
struct NiTriShape : NiGeometry
@ -149,6 +188,7 @@ struct NiTriShape : NiGeometry
Node::read(nif);
data.read(nif);
skin.read(nif);
materialData.read(nif);
}
void post(NIFFile *nif)
@ -170,6 +210,7 @@ struct NiTriStrips : NiGeometry
Node::read(nif);
data.read(nif);
skin.read(nif);
materialData.read(nif);
}
void post(NIFFile *nif)
@ -207,6 +248,8 @@ struct NiCamera : Node
{
struct Camera
{
unsigned short cameraFlags{0};
// Camera frustrum
float left, right, top, bottom, nearDist, farDist;
@ -216,15 +259,21 @@ struct NiCamera : Node
// Level of detail modifier
float LOD;
// Orthographic projection usage flag
bool orthographic{false};
void read(NIFStream *nif)
{
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
cameraFlags = nif->getUShort();
left = nif->getFloat();
right = nif->getFloat();
top = nif->getFloat();
bottom = nif->getFloat();
nearDist = nif->getFloat();
farDist = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
orthographic = nif->getBoolean();
vleft = nif->getFloat();
vright = nif->getFloat();
vtop = nif->getFloat();
@ -243,6 +292,8 @@ struct NiCamera : Node
nif->getInt(); // -1
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.
struct NiSwitchNode : public NiNode
{
unsigned int switchFlags{0};
unsigned int initialIndex;
void read(NIFStream *nif)
{
NiNode::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
switchFlags = nif->getUShort();
initialIndex = nif->getUInt();
}
};
@ -310,6 +364,12 @@ struct NiLODNode : public NiSwitchNode
NiSwitchNode::read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
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();
for (unsigned int i=0; i<numLodLevels; ++i)
{

View file

@ -6,25 +6,44 @@
namespace Nif
{
void Property::read(NIFStream *nif)
{
Named::read(nif);
flags = nif->getUShort();
}
void NiTexturingProperty::Texture::read(NIFStream *nif)
{
inUse = nif->getBoolean();
if(!inUse) return;
texture.read(nif);
clamp = nif->getUInt();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
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.
if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
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)
@ -35,6 +54,9 @@ void NiTexturingProperty::Texture::post(NIFFile *nif)
void NiTexturingProperty::read(NIFStream *nif)
{
Property::read(nif);
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();
@ -51,32 +73,53 @@ void NiTexturingProperty::read(NIFStream *nif)
envMapLumaBias = nif->getVector2();
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)
{
Property::post(nif);
for(int i = 0;i < 7;i++)
for (size_t i = 0; i < textures.size(); i++)
textures[i].post(nif);
for (size_t i = 0; i < shaderTextures.size(); i++)
shaderTextures[i].post(nif);
}
void NiFogProperty::read(NIFStream *nif)
{
Property::read(nif);
mFlags = nif->getUShort();
mFogDepth = nif->getFloat();
mColour = nif->getVector3();
}
void S_MaterialProperty::read(NIFStream *nif)
{
if (nif->getBethVersion() < 26)
{
ambient = nif->getVector3();
diffuse = nif->getVector3();
}
specular = nif->getVector3();
emissive = nif->getVector3();
glossiness = nif->getFloat();
alpha = nif->getFloat();
if (nif->getBethVersion() > 21)
emissive *= nif->getFloat();
}
void S_VertexColorProperty::read(NIFStream *nif)
@ -91,6 +134,8 @@ void S_AlphaProperty::read(NIFStream *nif)
}
void S_StencilProperty::read(NIFStream *nif)
{
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
enabled = nif->getChar();
compareFunc = nif->getInt();
@ -101,6 +146,19 @@ void S_StencilProperty::read(NIFStream *nif)
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