From 87290cf6d10819ad122949c8f704b9c29125d2bc Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 6 May 2020 23:26:16 +0200 Subject: [PATCH 01/66] Add support for multi configurations generators on unix Since version 3.17 cmake supports the Ninja Multi-Config No change for Xcode, VS and Ninja "single config" --- CMakeLists.txt | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 444743d28..ba56b5cc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -437,34 +437,40 @@ endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clan IF(NOT WIN32 AND NOT APPLE) # Linux installation + get_generator_is_multi_config(multi_config) + if (multi_config) + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") + else () + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") + endif () # Install binaries IF(BUILD_OPENMW) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENMW) IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) IF(BUILD_BSATOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/bsatool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BSATOOL) IF(BUILD_ESMTOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) IF(BUILD_NIFTEST) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/niftest" DESTINATION "${BINDIR}" ) ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) IF(BUILD_ESSIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-essimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-cs" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-cs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-wizard" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) # Install licenses @@ -480,17 +486,17 @@ IF(NOT WIN32 AND NOT APPLE) 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 "${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") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") + INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") + INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") ENDIF(NOT WIN32 AND NOT APPLE) From db0f7c607fad78a5a7632377e37ebd97abc901b3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 May 2020 14:04:42 +0100 Subject: [PATCH 02/66] Make Windows install target slightly less breakable --- CMakeLists.txt | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba56b5cc3..61e652455 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -501,31 +501,25 @@ IF(NOT WIN32 AND NOT APPLE) 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) + get_generator_is_multi_config(multi_config) + if (multi_config) + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") + else () + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") + endif () + + INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll") + 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 "${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/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug) - INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + INSTALL(FILES + "${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt" + DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".") - 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) + INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") SET(CPACK_PACKAGE_NAME "OpenMW") From d0ddf488dbb3406db29cecd41f94c365f06caa82 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 May 2020 14:10:13 +0100 Subject: [PATCH 03/66] Install PDBs when appropriate --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61e652455..f899d3a30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -509,6 +509,7 @@ if(WIN32) endif () INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll") + INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb") 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") From 8dd820ba48668c58e5240a77effc200e6a905607 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 May 2020 17:30:12 +0000 Subject: [PATCH 04/66] Exclude deps --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f899d3a30..e65e1debe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -508,8 +508,8 @@ if(WIN32) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () - INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll") - INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb") + INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll" EXCLUDE "deps/*") + INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb" EXCLUDE "deps/*") 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") From 73708a6f2a56988ebc63ee43bf2f3ef3c0fad84b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 May 2020 17:39:25 +0000 Subject: [PATCH 05/66] Install openmw-cs.cfg on single-config generators and from the right directory on multi-config --- apps/opencs/CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index dc9b98596..b20920904 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -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}/$") + else () + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") + endif () + + INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION ".") endif() if (MSVC) From be5fd6fd03bc3f86ee8764ce33b1e10556fb5010 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 May 2020 21:43:41 +0100 Subject: [PATCH 06/66] Exclude directories correctly We don't need `/*` as we don't want the directory itself, not just its contents. We also need to list possible other directories explicitly as there's currently no way to skip directories without matches. It would be much nicer if CMake was tracking the DLLs we needed for us. --- CMakeLists.txt | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e65e1debe..41cb066e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -508,8 +508,24 @@ if(WIN32) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () - INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll" EXCLUDE "deps/*") - INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb" EXCLUDE "deps/*") + 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") From ae38b3d9b223feea5e3dd033f5698dd4d6de5140 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 30 Aug 2020 11:29:44 +0200 Subject: [PATCH 07/66] Put the install logic in one place for all platforms --- CMakeLists.txt | 320 ++++++++++++++++++++++++------------------------- 1 file changed, 156 insertions(+), 164 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41cb066e4..0e7ea6800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,169 +435,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 - get_generator_is_multi_config(multi_config) - if (multi_config) - SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") - else () - SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") - endif () - - # Install binaries - IF(BUILD_OPENMW) - INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_OPENMW) - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_LAUNCHER) - 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") - 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") - - 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(NOT WIN32 AND NOT APPLE) - -if(WIN32) - get_generator_is_multi_config(multi_config) - if (multi_config) - SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") - else () - SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") - endif () - - 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/DejaVu Font License.txt" - DESTINATION ".") - INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") - 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) - 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/windows/openmw.ico") - SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.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 set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") @@ -892,7 +729,162 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") " COMPONENT Runtime) include(CPack) -endif () +elseif(NOT APPLE) + get_generator_is_multi_config(multi_config) + if (multi_config) + SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") + 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/DejaVu Font License.txt" + DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") + 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) + 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/windows/openmw.ico") + SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.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) + else(WIN32) + # Linux installation + + # Install binaries + IF(BUILD_OPENMW) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENMW) + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_LAUNCHER) + 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") + 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") + + 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(NOT APPLE) # Doxygen Target -- simply run 'make doc' or 'make doc_pages' # output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen" From 5ab29f26e94f757a97c3f11234f3e80d9d3b00d2 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 1 Oct 2020 20:27:48 +0300 Subject: [PATCH 08/66] Put the main menu below other windows (bug #5622) --- CHANGELOG.md | 1 + files/mygui/openmw_layers.xml | 1 + files/mygui/openmw_mainmenu.layout | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a00716f96..5ecf9d85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Bug #5557: Diagonal movement is noticeably slower with analogue stick 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 diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index a98efe07c..a090fb369 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -6,6 +6,7 @@ + diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index c4d88ff85..533ba1317 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -2,7 +2,7 @@ - + From 4e22c2687f1f1606ed91bc6237222dbd26b14f5d Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 2 Oct 2020 21:34:42 +0300 Subject: [PATCH 09/66] niffile.cpp cleanup --- components/nif/niffile.cpp | 165 ++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 87 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b33f0c051..118a58299 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -16,105 +16,96 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) NIFFile::~NIFFile() { - for (std::vector::iterator it = records.begin() ; it != records.end(); ++it) - { - delete *it; - } + for (Record* record : records) + delete record; } template 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 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 makeFactory() { - std::map newFactory; - newFactory.insert(makeEntry("NiNode", &construct , RC_NiNode )); - newFactory.insert(makeEntry("NiSwitchNode", &construct , RC_NiSwitchNode )); - newFactory.insert(makeEntry("NiLODNode", &construct , RC_NiLODNode )); - newFactory.insert(makeEntry("AvoidNode", &construct , RC_AvoidNode )); - newFactory.insert(makeEntry("NiCollisionSwitch", &construct , RC_NiCollisionSwitch )); - newFactory.insert(makeEntry("NiBSParticleNode", &construct , RC_NiBSParticleNode )); - newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); - newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); - newFactory.insert(makeEntry("NiTriShape", &construct , RC_NiTriShape )); - newFactory.insert(makeEntry("NiTriStrips", &construct , RC_NiTriStrips )); - newFactory.insert(makeEntry("NiLines", &construct , RC_NiLines )); - newFactory.insert(makeEntry("NiRotatingParticles", &construct , RC_NiRotatingParticles )); - newFactory.insert(makeEntry("NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles )); - newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); - newFactory.insert(makeEntry("RootCollisionNode", &construct , RC_RootCollisionNode )); - newFactory.insert(makeEntry("NiTexturingProperty", &construct , RC_NiTexturingProperty )); - newFactory.insert(makeEntry("NiFogProperty", &construct , RC_NiFogProperty )); - newFactory.insert(makeEntry("NiMaterialProperty", &construct , RC_NiMaterialProperty )); - newFactory.insert(makeEntry("NiZBufferProperty", &construct , RC_NiZBufferProperty )); - newFactory.insert(makeEntry("NiAlphaProperty", &construct , RC_NiAlphaProperty )); - newFactory.insert(makeEntry("NiVertexColorProperty", &construct , RC_NiVertexColorProperty )); - newFactory.insert(makeEntry("NiShadeProperty", &construct , RC_NiShadeProperty )); - newFactory.insert(makeEntry("NiDitherProperty", &construct , RC_NiDitherProperty )); - newFactory.insert(makeEntry("NiWireframeProperty", &construct , RC_NiWireframeProperty )); - newFactory.insert(makeEntry("NiSpecularProperty", &construct , RC_NiSpecularProperty )); - newFactory.insert(makeEntry("NiStencilProperty", &construct , RC_NiStencilProperty )); - newFactory.insert(makeEntry("NiVisController", &construct , RC_NiVisController )); - newFactory.insert(makeEntry("NiGeomMorpherController", &construct , RC_NiGeomMorpherController )); - newFactory.insert(makeEntry("NiKeyframeController", &construct , RC_NiKeyframeController )); - newFactory.insert(makeEntry("NiAlphaController", &construct , RC_NiAlphaController )); - newFactory.insert(makeEntry("NiRollController", &construct , RC_NiRollController )); - newFactory.insert(makeEntry("NiUVController", &construct , RC_NiUVController )); - newFactory.insert(makeEntry("NiPathController", &construct , RC_NiPathController )); - newFactory.insert(makeEntry("NiMaterialColorController", &construct , RC_NiMaterialColorController )); - newFactory.insert(makeEntry("NiBSPArrayController", &construct , RC_NiBSPArrayController )); - newFactory.insert(makeEntry("NiParticleSystemController", &construct , RC_NiParticleSystemController )); - newFactory.insert(makeEntry("NiFlipController", &construct , RC_NiFlipController )); - newFactory.insert(makeEntry("NiAmbientLight", &construct , RC_NiLight )); - newFactory.insert(makeEntry("NiDirectionalLight", &construct , RC_NiLight )); - newFactory.insert(makeEntry("NiPointLight", &construct , RC_NiLight )); - newFactory.insert(makeEntry("NiSpotLight", &construct , RC_NiLight )); - newFactory.insert(makeEntry("NiTextureEffect", &construct , RC_NiTextureEffect )); - newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData )); - newFactory.insert(makeEntry("NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData )); - newFactory.insert(makeEntry("NiStringExtraData", &construct , RC_NiStringExtraData )); - newFactory.insert(makeEntry("NiGravity", &construct , RC_NiGravity )); - newFactory.insert(makeEntry("NiPlanarCollider", &construct , RC_NiPlanarCollider )); - newFactory.insert(makeEntry("NiSphericalCollider", &construct , RC_NiSphericalCollider )); - newFactory.insert(makeEntry("NiParticleGrowFade", &construct , RC_NiParticleGrowFade )); - newFactory.insert(makeEntry("NiParticleColorModifier", &construct , RC_NiParticleColorModifier )); - newFactory.insert(makeEntry("NiParticleRotation", &construct , RC_NiParticleRotation )); - newFactory.insert(makeEntry("NiFloatData", &construct , RC_NiFloatData )); - newFactory.insert(makeEntry("NiTriShapeData", &construct , RC_NiTriShapeData )); - newFactory.insert(makeEntry("NiTriStripsData", &construct , RC_NiTriStripsData )); - newFactory.insert(makeEntry("NiLinesData", &construct , RC_NiLinesData )); - newFactory.insert(makeEntry("NiVisData", &construct , RC_NiVisData )); - newFactory.insert(makeEntry("NiColorData", &construct , RC_NiColorData )); - newFactory.insert(makeEntry("NiPixelData", &construct , RC_NiPixelData )); - newFactory.insert(makeEntry("NiMorphData", &construct , RC_NiMorphData )); - newFactory.insert(makeEntry("NiKeyframeData", &construct , RC_NiKeyframeData )); - newFactory.insert(makeEntry("NiSkinData", &construct , RC_NiSkinData )); - newFactory.insert(makeEntry("NiUVData", &construct , RC_NiUVData )); - newFactory.insert(makeEntry("NiPosData", &construct , RC_NiPosData )); - newFactory.insert(makeEntry("NiRotatingParticlesData", &construct , RC_NiRotatingParticlesData )); - newFactory.insert(makeEntry("NiAutoNormalParticlesData", &construct , RC_NiAutoNormalParticlesData )); - newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct , RC_NiSequenceStreamHelper )); - newFactory.insert(makeEntry("NiSourceTexture", &construct , RC_NiSourceTexture )); - newFactory.insert(makeEntry("NiSkinInstance", &construct , RC_NiSkinInstance )); - newFactory.insert(makeEntry("NiLookAtController", &construct , RC_NiLookAtController )); - newFactory.insert(makeEntry("NiPalette", &construct , RC_NiPalette )); - return newFactory; + std::map factory; + factory["NiNode"] = {&construct , RC_NiNode }; + factory["NiSwitchNode"] = {&construct , RC_NiSwitchNode }; + factory["NiLODNode"] = {&construct , RC_NiLODNode }; + factory["AvoidNode"] = {&construct , RC_AvoidNode }; + factory["NiCollisionSwitch"] = {&construct , RC_NiCollisionSwitch }; + factory["NiBSParticleNode"] = {&construct , RC_NiBSParticleNode }; + factory["NiBSAnimationNode"] = {&construct , RC_NiBSAnimationNode }; + factory["NiBillboardNode"] = {&construct , RC_NiBillboardNode }; + factory["NiTriShape"] = {&construct , RC_NiTriShape }; + factory["NiTriStrips"] = {&construct , RC_NiTriStrips }; + factory["NiLines"] = {&construct , RC_NiLines }; + factory["NiRotatingParticles"] = {&construct , RC_NiRotatingParticles }; + factory["NiAutoNormalParticles"] = {&construct , RC_NiAutoNormalParticles }; + factory["NiCamera"] = {&construct , RC_NiCamera }; + factory["RootCollisionNode"] = {&construct , RC_RootCollisionNode }; + factory["NiTexturingProperty"] = {&construct , RC_NiTexturingProperty }; + factory["NiFogProperty"] = {&construct , RC_NiFogProperty }; + factory["NiMaterialProperty"] = {&construct , RC_NiMaterialProperty }; + factory["NiZBufferProperty"] = {&construct , RC_NiZBufferProperty }; + factory["NiAlphaProperty"] = {&construct , RC_NiAlphaProperty }; + factory["NiVertexColorProperty"] = {&construct , RC_NiVertexColorProperty }; + factory["NiShadeProperty"] = {&construct , RC_NiShadeProperty }; + factory["NiDitherProperty"] = {&construct , RC_NiDitherProperty }; + factory["NiWireframeProperty"] = {&construct , RC_NiWireframeProperty }; + factory["NiSpecularProperty"] = {&construct , RC_NiSpecularProperty }; + factory["NiStencilProperty"] = {&construct , RC_NiStencilProperty }; + factory["NiVisController"] = {&construct , RC_NiVisController }; + factory["NiGeomMorpherController"] = {&construct , RC_NiGeomMorpherController }; + factory["NiKeyframeController"] = {&construct , RC_NiKeyframeController }; + factory["NiAlphaController"] = {&construct , RC_NiAlphaController }; + factory["NiRollController"] = {&construct , RC_NiRollController }; + factory["NiUVController"] = {&construct , RC_NiUVController }; + factory["NiPathController"] = {&construct , RC_NiPathController }; + factory["NiMaterialColorController"] = {&construct , RC_NiMaterialColorController }; + factory["NiBSPArrayController"] = {&construct , RC_NiBSPArrayController }; + factory["NiParticleSystemController"] = {&construct , RC_NiParticleSystemController }; + factory["NiFlipController"] = {&construct , RC_NiFlipController }; + factory["NiAmbientLight"] = {&construct , RC_NiLight }; + factory["NiDirectionalLight"] = {&construct , RC_NiLight }; + factory["NiPointLight"] = {&construct , RC_NiLight }; + factory["NiSpotLight"] = {&construct , RC_NiLight }; + factory["NiTextureEffect"] = {&construct , RC_NiTextureEffect }; + factory["NiVertWeightsExtraData"] = {&construct , RC_NiVertWeightsExtraData }; + factory["NiTextKeyExtraData"] = {&construct , RC_NiTextKeyExtraData }; + factory["NiStringExtraData"] = {&construct , RC_NiStringExtraData }; + factory["NiGravity"] = {&construct , RC_NiGravity }; + factory["NiPlanarCollider"] = {&construct , RC_NiPlanarCollider }; + factory["NiSphericalCollider"] = {&construct , RC_NiSphericalCollider }; + factory["NiParticleGrowFade"] = {&construct , RC_NiParticleGrowFade }; + factory["NiParticleColorModifier"] = {&construct , RC_NiParticleColorModifier }; + factory["NiParticleRotation"] = {&construct , RC_NiParticleRotation }; + factory["NiFloatData"] = {&construct , RC_NiFloatData }; + factory["NiTriShapeData"] = {&construct , RC_NiTriShapeData }; + factory["NiTriStripsData"] = {&construct , RC_NiTriStripsData }; + factory["NiLinesData"] = {&construct , RC_NiLinesData }; + factory["NiVisData"] = {&construct , RC_NiVisData }; + factory["NiColorData"] = {&construct , RC_NiColorData }; + factory["NiPixelData"] = {&construct , RC_NiPixelData }; + factory["NiMorphData"] = {&construct , RC_NiMorphData }; + factory["NiKeyframeData"] = {&construct , RC_NiKeyframeData }; + factory["NiSkinData"] = {&construct , RC_NiSkinData }; + factory["NiUVData"] = {&construct , RC_NiUVData }; + factory["NiPosData"] = {&construct , RC_NiPosData }; + factory["NiRotatingParticlesData"] = {&construct , RC_NiRotatingParticlesData }; + factory["NiAutoNormalParticlesData"] = {&construct , RC_NiAutoNormalParticlesData }; + factory["NiSequenceStreamHelper"] = {&construct , RC_NiSequenceStreamHelper }; + factory["NiSourceTexture"] = {&construct , RC_NiSourceTexture }; + factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; + factory["NiLookAtController"] = {&construct , RC_NiLookAtController }; + factory["NiPalette"] = {&construct , RC_NiPalette }; + return factory; } @@ -149,7 +140,7 @@ void NIFFile::parse(Files::IStreamPtr stream) if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW) fail("Unsupported NIF version: " + printVersion(ver)); // Number of records - size_t recNum = nif.getInt(); + size_t recNum = nif.getUInt(); records.resize(recNum); for(size_t i = 0;i < recNum;i++) @@ -201,8 +192,8 @@ void NIFFile::parse(Files::IStreamPtr stream) } // Once parsing is done, do post-processing. - for(size_t i=0; ipost(this); + for (Record* record : records) + record->post(this); } void NIFFile::setUseSkinning(bool skinning) From 1ddfb18cb35877fa3965b7dcb8f2cf075f1a722d Mon Sep 17 00:00:00 2001 From: psi29a Date: Fri, 2 Oct 2020 21:07:46 +0000 Subject: [PATCH 10/66] Update CI/before_script.msvc.sh --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index e8d65372d..8b3ece2bd 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -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 From 23fe60a06788352f6140a2570869d1352cf30c8b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 2 Oct 2020 22:46:23 +0200 Subject: [PATCH 11/66] Run unit tests in a separate build --- .travis.yml | 8 +++++- CI/before_script.linux.sh | 51 +++++++++++++++++++++++++++------------ CI/build_googletest.sh | 12 ++++++--- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36b15d794..8d98d0dd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,12 @@ matrix: os: linux dist: focal if: branch != coverity_scan + - name: OpenMW (tests only) on Ubuntu Focal with GCC + os: linux + dist: focal + if: branch != coverity_scan + env: + - BUILD_TESTS_ONLY: 1 - name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis os: linux dist: focal @@ -73,7 +79,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 diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index f4aaeecc9..6df3dc32e 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -2,22 +2,43 @@ free -m -env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh -GOOGLETEST_DIR="$(pwd)/googletest/build" +if [[ "${BUILD_TESTS_ONLY}" ]]; then + export GOOGLETEST_DIR="$(pwd)/googletest/build/install" + env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh +fi mkdir build cd build -${ANALYZE} cmake \ - -DCMAKE_C_COMPILER="${CC}" \ - -DCMAKE_CXX_COMPILER="${CXX}" \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DBUILD_UNITTESTS=TRUE \ - -DUSE_SYSTEM_TINYXML=TRUE \ - -DCMAKE_INSTALL_PREFIX="/usr" \ - -DBINDIR="/usr/games" \ - -DCMAKE_BUILD_TYPE="DEBUG" \ - -DGTEST_ROOT="${GOOGLETEST_DIR}" \ - -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ - .. +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_BSATOOL=OFF \ + -D BUILD_ESMTOOL=OFF \ + -D BUILD_LAUNCHER=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 USE_SYSTEM_TINYXML=TRUE \ + -D CMAKE_INSTALL_PREFIX=install \ + -D CMAKE_BUILD_TYPE=Debug \ + .. +fi diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh index 0ffda7f9b..a9a50fee7 100755 --- a/CI/build_googletest.sh +++ b/CI/build_googletest.sh @@ -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}" From af95474670e88b3deb1b15ce0a396327169a0b97 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 2 Oct 2020 20:58:03 +0200 Subject: [PATCH 12/66] Run unit tests in gitlab CI for linux build --- .gitlab-ci.yml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d171e8222..97bd619c3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ stages: - build - -Debian: + +.Debian: tags: - docker - linux @@ -14,20 +14,34 @@ Debian: before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake 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 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 1G + - 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 + variables: + CC: gcc + CXX: g++ + +Debian_GCC_tests: + extends: .Debian + variables: + CC: gcc + CXX: g++ + BUILD_TESTS_ONLY: 1 MacOS: tags: @@ -228,4 +242,4 @@ Windows_MSBuild_CS_RelWithDebInfo: - .Windows_MSBuild_Base variables: <<: *cs-targets - config: "RelWithDebInfo" \ No newline at end of file + config: "RelWithDebInfo" From 322298e02a5f0367d610e13610afb214793f556c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 2 Oct 2020 22:45:25 +0200 Subject: [PATCH 13/66] Build with clang in gitlab CI for linux --- .gitlab-ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97bd619c3..2cb111a1a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ stages: 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 git + - 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`" @@ -43,6 +43,19 @@ Debian_GCC_tests: CXX: g++ BUILD_TESTS_ONLY: 1 +Debian_Clang: + extends: .Debian + variables: + CC: clang + CXX: clang++ + +Debian_Clang_tests: + extends: .Debian + variables: + CC: clang + CXX: clang++ + BUILD_TESTS_ONLY: 1 + MacOS: tags: - macos From 9e78f3d936c95b6add1d097f957e5a770beac813 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 3 Oct 2020 02:19:38 +0100 Subject: [PATCH 14/66] Skip raycasting for zero-length rays Rays with no length can't intersect anything. This also prevents an assertion triggering with Bullet built in Debug mode. --- apps/openmw/mwphysics/physicssystem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 91e7a9de0..8b07fea4b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -264,6 +264,8 @@ namespace MWPhysics RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { + if (from == to) + return RayCastingResult { false }; btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); From 5215ce45d08623c4d16ab345d1b1613a59628d83 Mon Sep 17 00:00:00 2001 From: imkirkdouglacus Date: Sat, 3 Oct 2020 10:51:27 +0000 Subject: [PATCH 15/66] Update install-game-files.rst --- .../manuals/installation/install-game-files.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index ac3688e88..538cfd4c6 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -105,6 +105,19 @@ If you are running macOS, you can also download Morrowind through Steam: #. Launch the Steam client and let it download. You can then find ``Morrowind.esm`` at ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` +Linux +---- +Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". +---- +#. Install Steam from "Ubuntu Software" Center +#. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles" +#. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick. +#. Once the game files are installed, we can now install the open OpenMW Engine. I used "OpenMW launcher" from "Ubuntu Software" Center this has a wizard to help with the basic setup of OpenMW. +#. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'. +#. Everything should now be in place, click that big "PLAY" button and fire up OpenMW. + +Nb. Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you dont have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. + Wine ---- From 845049a166b2c3ea02186b85bdba54398a15006c Mon Sep 17 00:00:00 2001 From: Kyle Shrader Date: Sat, 3 Oct 2020 10:55:44 +0000 Subject: [PATCH 16/66] Prevent empty right page of journal having invisible topics (Fixes #5588) --- CHANGELOG.md | 1 + apps/openmw/mwgui/bookpage.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a00716f96..a89680a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ 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 Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 5724defcd..47027c2b7 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -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); From d5450a7d88c3cab9931cf14720dc286cb9ea288d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 3 Oct 2020 14:22:34 +0200 Subject: [PATCH 17/66] Correctly resetting line numbering during shader processing. --- apps/openmw_test_suite/shader/shadermanager.cpp | 2 +- components/shader/shadermanager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw_test_suite/shader/shadermanager.cpp b/apps/openmw_test_suite/shader/shadermanager.cpp index e823d5fe2..a25e5e9ba 100644 --- a/apps/openmw_test_suite/shader/shadermanager.cpp +++ b/apps/openmw_test_suite/shader/shadermanager.cpp @@ -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); diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 8523a2962..bfaa11282 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -106,7 +106,7 @@ namespace Shader else { lineDirectivePosition = 0; - lineNumber = 1; + lineNumber = 0; } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); From 14aacb81c580b4581b90ffdca9db9264bfbcd644 Mon Sep 17 00:00:00 2001 From: Charles Calhoun <6294115-ccalhoun1999@users.noreply.gitlab.com> Date: Sat, 3 Oct 2020 20:05:17 +0000 Subject: [PATCH 18/66] Fix follower aggression when traveling. Summoning still has problems but less intrusive than current implementation. --- CHANGELOG.md | 1 + apps/openmw/mwgui/travelwindow.cpp | 2 +- apps/openmw/mwworld/actionteleport.cpp | 18 +++++++++++++----- apps/openmw/mwworld/actionteleport.hpp | 5 +++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a89680a90..fc0b2848c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ 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 diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index a730f95c6..ed7a74b95 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -74,7 +74,7 @@ namespace MWGui // Add price for the travelling followers std::set 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(followers.size()); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index f54edc8cb..9cd8469a3 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -24,7 +24,7 @@ namespace MWWorld { // Find any NPCs that are following the actor and teleport them with him std::set followers; - getFollowersToTeleport(actor, followers); + getFollowers(actor, followers, true); for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) teleport(*it); @@ -47,7 +47,9 @@ namespace MWWorld } else { - 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; @@ -60,7 +62,7 @@ namespace MWWorld } } - void ActionTeleport::getFollowersToTeleport(const MWWorld::Ptr& actor, std::set& out) { + void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles) { std::set followers; MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); @@ -69,11 +71,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); } } } diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index c58218750..bd7b236a4 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -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& 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& out, bool includeHostiles = false); }; } From 2835ea1e21bc99623676f65d1552d93559dfec2b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 3 Oct 2020 22:55:21 +0200 Subject: [PATCH 19/66] Update logic of "NPCs avoid collisions" --- apps/openmw/mwmechanics/actors.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3de1a3acc..822ed8d92 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1668,8 +1668,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(); @@ -1680,9 +1681,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 @@ -1710,11 +1717,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); @@ -1730,9 +1737,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. @@ -1767,16 +1775,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(); From 29ccb09da5bc3ba2c1acb0af776f5f8ba59c85ce Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 4 Oct 2020 01:27:49 +0300 Subject: [PATCH 20/66] Introduce some extended NIF definitions --- .../nifloader/testbulletnifloader.cpp | 1 + components/nif/base.hpp | 17 ++- components/nif/controlled.cpp | 27 +++- components/nif/controller.cpp | 16 ++- components/nif/controller.hpp | 1 + components/nif/data.cpp | 135 ++++++++++++++---- components/nif/data.hpp | 13 +- components/nif/effect.cpp | 18 +-- components/nif/effect.hpp | 3 + components/nif/niffile.cpp | 75 +++++++++- components/nif/nifstream.cpp | 9 +- components/nif/node.hpp | 74 +++++++++- components/nif/property.cpp | 106 ++++++++++---- components/nif/property.hpp | 110 +++++++++++--- components/nifosg/nifloader.cpp | 7 +- 15 files changed, 505 insertions(+), 107 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 30903b897..72dcd3066 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -244,6 +244,7 @@ namespace void init(Nif::Named& value) { value.extra = Nif::ExtraPtr(nullptr); + value.extralist = Nif::ExtraList(); value.controller = Nif::ControllerPtr(nullptr); } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 6e26f525e..0d42131e5 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -14,12 +14,18 @@ namespace Nif class Extra : public Record { public: + std::string name; ExtraPtr next; // Next extra data record in the list void read(NIFStream *nif) { - next.read(nif); - nif->getUInt(); // Size of the record + 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(); - extra.read(nif); + 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); } }; diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index 0b5c32a10..ab2b8dc17 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -14,16 +14,31 @@ namespace Nif if (external) filename = nif->getString(); else - internal = nif->getChar(); - - if (!external && internal) + { + 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) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index c63c83676..8d943b58a 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -97,7 +97,10 @@ namespace Nif // 01: Diffuse // 10: Specular // 11: Emissive - targetColor = (flags >> 4) & 3; + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + targetColor = nif->getUShort() & 3; + else + targetColor = (flags >> 4) & 3; data.read(nif); } @@ -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); } @@ -192,6 +197,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 +226,11 @@ namespace Nif { Controller::read(nif); mTexSlot = nif->getUInt(); - /*unknown=*/nif->getUInt();/*0?*/ - mDelta = nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) + { + timeStart = nif->getFloat(); + mDelta = nif->getFloat(); + } mSources.read(nif); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 41dd14fac..bf043fbdb 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -118,6 +118,7 @@ class NiLookAtController : public Controller { public: NodePtr target; + unsigned short lookAtFlags{0}; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index afb304bad..e76541d5c 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -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); - // In Morrowind this field only corresponds to the number of UV sets. - // NifTools research is inaccurate. - int uvs = nif->getUShort(); - - if(nif->getInt()) + // 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)) { - uvlist.resize(uvs); - for(int i = 0;i < uvs;i++) + numUVs = nif->getUShort(); + // In Morrowind this field only corresponds to the number of UV sets. + // NifTools research is inaccurate. + if (nif->getVersion() > NIFFile::NIFVersion::VER_MW) + numUVs &= 0x3f; + } + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) + numUVs &= 0x1; + + bool hasUVs = true; + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + hasUVs = nif->getBoolean(); + if (hasUVs) + { + uvlist.resize(numUVs); + for (unsigned int i = 0; i < numUVs; i++) { nif->getVector2s(uvlist[i], verts); // 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(); - nif->getUShorts(triangles, cnt); + bool hasTriangles = true; + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) + hasTriangles = nif->getBoolean(); + if (hasTriangles) + nif->getUShorts(triangles, cnt); // Read the match list, which lists the vertices that are equal to // 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 lengths; nif->getUShorts(lengths, numStrips); - if (!numStrips) + // "Has Strips" flag. Exceptionally useful. + bool hasStrips = false; + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) + hasStrips = nif->getBoolean(); + if (!hasStrips || !numStrips) return; strips.resize(numStrips); @@ -140,27 +187,37 @@ void NiAutoNormalParticlesData::read(NIFStream *nif) NiGeometryData::read(nif); // Should always match the number of vertices - numParticles = nif->getUShort(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + numParticles = nif->getUShort(); - particleRadius = nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) + std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat()); + else if (nif->getBoolean()) + nif->getFloats(particleRadii, vertices.size()); activeCount = nif->getUShort(); + // Particle sizes if (nif->getBoolean()) - { - // Particle sizes 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(); - for (unsigned int i = 0; i < 4; ++i) - colorMask[i] = nif->getUInt(); - bpp = 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,7 +368,8 @@ void NiKeyframeData::read(NIFStream *nif) if(mRotations->mInterpolationType == InterpolationType_XYZ) { //Chomp unused float - nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) + nif->getFloat(); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 66b3f693a..1519bd1b3 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -35,7 +35,7 @@ namespace Nif class NiGeometryData : public Record { public: - std::vector vertices, normals; + std::vector vertices, normals, tangents, bitangents; std::vector colors; std::vector< std::vector > uvlist; osg::Vec3f center; @@ -73,13 +73,15 @@ struct NiLinesData : public NiGeometryData class NiAutoNormalParticlesData : public NiGeometryData { public: - int numParticles; + int numParticles{0}; float particleRadius; int activeCount; - std::vector sizes; + std::vector particleRadii, sizes, rotationAngles; + std::vector rotations; + std::vector rotationAxes; void read(NIFStream *nif); }; @@ -87,8 +89,6 @@ public: class NiRotatingParticlesData : public NiAutoNormalParticlesData { public: - std::vector rotations; - void read(NIFStream *nif); }; @@ -133,7 +133,8 @@ public: Format fmt; unsigned int colorMask[4]; - unsigned int bpp; + unsigned int bpp, pixelTiling{0}; + bool sRGB{false}; NiPalettePtr palette; unsigned int numberOfMipmaps; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 7947e301d..c12eb6c1b 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -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) diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 453e4b04c..818df90a2 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -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; igetUInt(); // ref to another Node diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 118a58299..31c959964 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -139,15 +139,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.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 recTypes; + std::vector 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 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 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; @@ -155,6 +217,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::const_iterator entry = factories.find(rec); if (entry != factories.end()) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 44be4b241..69f1a905b 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -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(); } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e605df32a..4c57b2e81 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -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,12 +39,15 @@ public: { Named::read(nif); - flags = nif->getUShort(); + flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); trafo = nif->getTrafo(); - velocity = nif->getVector3(); - props.read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + velocity = nif->getVector3(); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + props.read(nif); - hasBounds = nif->getBoolean(); + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + hasBounds = nif->getBoolean(); if(hasBounds) { nif->getInt(); // always 1 @@ -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,7 +108,8 @@ struct NiNode : Node { Node::read(nif); children.read(nif); - effects.read(nif); + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) + effects.read(nif); // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but @@ -130,7 +137,39 @@ struct NiNode : Node struct NiGeometry : Node { + struct MaterialData + { + std::vector materialNames; + std::vector 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; igetUShort(); -} - void NiTexturingProperty::Texture::read(NIFStream *nif) { inUse = nif->getBoolean(); if(!inUse) return; texture.read(nif); - clamp = nif->getUInt(); - nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible - uvSet = 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. - nif->skip(4); - nif->skip(2); // Unknown short + 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,7 +54,10 @@ void NiTexturingProperty::Texture::post(NIFFile *nif) void NiTexturingProperty::read(NIFStream *nif) { Property::read(nif); - apply = nif->getUInt(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20,1,0,2)) + flags = nif->getUShort(); + if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,1)) + apply = nif->getUInt(); unsigned int numTextures = nif->getUInt(); @@ -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) { - ambient = nif->getVector3(); - diffuse = nif->getVector3(); + 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) @@ -92,14 +135,29 @@ void S_AlphaProperty::read(NIFStream *nif) void S_StencilProperty::read(NIFStream *nif) { - enabled = nif->getChar(); - compareFunc = nif->getInt(); - stencilRef = nif->getUInt(); - stencilMask = nif->getUInt(); - failAction = nif->getInt(); - zFailAction = nif->getInt(); - zPassAction = nif->getInt(); - drawMode = nif->getInt(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + enabled = nif->getChar(); + compareFunc = nif->getInt(); + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + failAction = nif->getInt(); + zFailAction = nif->getInt(); + zPassAction = nif->getInt(); + drawMode = nif->getInt(); + } + else + { + unsigned short flags = nif->getUShort(); + enabled = flags & 0x1; + failAction = (flags >> 1) & 0x7; + zFailAction = (flags >> 4) & 0x7; + zPassAction = (flags >> 7) & 0x7; + drawMode = (flags >> 10) & 0x3; + compareFunc = (flags >> 12) & 0x7; + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index c72dbf6ba..e20d948f0 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -29,18 +29,13 @@ namespace Nif { -class Property : public Named -{ -public: - // The meaning of these depends on the actual property type. - unsigned int flags; - - void read(NIFStream *nif); -}; +class Property : public Named { }; class NiTexturingProperty : public Property { public: + unsigned short flags{0u}; + // A sub-texture struct Texture { @@ -92,6 +87,7 @@ public: }; std::vector textures; + std::vector shaderTextures; osg::Vec2f envMapLumaBias; osg::Vec4f bumpMapMatrix; @@ -103,28 +99,81 @@ public: class NiFogProperty : public Property { public: + unsigned short mFlags; float mFogDepth; osg::Vec3f mColour; void read(NIFStream *nif); }; -// These contain no other data than the 'flags' field in Property -class NiShadeProperty : public Property { }; -class NiDitherProperty : public Property { }; -class NiZBufferProperty : public Property { }; -class NiSpecularProperty : public Property { }; -class NiWireframeProperty : public Property { }; +// These contain no other data than the 'flags' field +struct NiShadeProperty : public Property +{ + unsigned short flags{0u}; + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + flags = nif->getUShort(); + } +}; + +struct NiDitherProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + +struct NiZBufferProperty : public Property +{ + unsigned short flags; + unsigned int testFunction; + void read(NIFStream *nif) + { + Property::read(nif); + flags = nif->getUShort(); + testFunction = (flags >> 2) & 0x7; + if (nif->getVersion() >= NIFStream::generateVersion(4,1,0,12) && nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + testFunction = nif->getUInt(); + } +}; + +struct NiSpecularProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + +struct NiWireframeProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + // The rest are all struct-based template struct StructPropT : Property { T data; + unsigned short flags; void read(NIFStream *nif) { Property::read(nif); + flags = nif->getUShort(); data.read(nif); } }; @@ -132,7 +181,8 @@ struct StructPropT : Property struct S_MaterialProperty { // The vector components are R,G,B - osg::Vec3f ambient, diffuse, specular, emissive; + osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; + osg::Vec3f specular, emissive; float glossiness, alpha; void read(NIFStream *nif); @@ -246,9 +296,35 @@ struct S_StencilProperty }; class NiAlphaProperty : public StructPropT { }; -class NiMaterialProperty : public StructPropT { }; class NiVertexColorProperty : public StructPropT { }; -class NiStencilProperty : public StructPropT { }; +struct NiStencilProperty : public Property +{ + S_StencilProperty data; + unsigned short flags{0u}; + + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + flags = nif->getUShort(); + data.read(nif); + } +}; + +struct NiMaterialProperty : public Property +{ + S_MaterialProperty data; + unsigned short flags{0u}; + + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3,0,0,0) + && nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + flags = nif->getUShort(); + data.read(nif); + } +}; } // Namespace #endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f9274cebf..61a276dc1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1732,7 +1732,7 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); // Specular lighting is enabled by default, but there's a quirk... - int specFlags = 1; + bool specEnabled = true; osg::ref_ptr mat (new osg::Material); mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF); @@ -1751,7 +1751,8 @@ namespace NifOsg case Nif::RC_NiSpecularProperty: { // Specular property can turn specular lighting off. - specFlags = property->flags; + auto specprop = static_cast(property); + specEnabled = specprop->flags & 1; break; } case Nif::RC_NiMaterialProperty: @@ -1835,7 +1836,7 @@ namespace NifOsg } // While NetImmerse and Gamebryo support specular lighting, Morrowind has its support disabled. - if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || specFlags == 0) + if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || !specEnabled) mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); if (lightmode == 0) From 822764d0facaf5cc2787c5391cf90f1afa20ebd0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 27 Sep 2020 13:00:41 +0400 Subject: [PATCH 21/66] Rework fixed strings handling --- .../esm/test_fixed_string.cpp | 2 -- components/esm/esmcommon.hpp | 25 ++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index dc88a5f63..bd598cc93 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -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::value); ASSERT_TRUE(std::is_pod::value); ASSERT_TRUE(std::is_pod::value); - ASSERT_TRUE(std::is_pod::value); } diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index f7a8bf126..232a24fcf 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -6,7 +6,6 @@ #include #include -#include 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 const* self() const @@ -103,6 +107,20 @@ struct FIXED_STRING<4> : public FIXED_STRING_BASE 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 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 From 03b081137aa7a3170fdcf81f90917eba8caec5c2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 5 Oct 2020 20:38:31 +0000 Subject: [PATCH 22/66] Correct screenshot paths in documentation These have been wrong since https://github.com/OpenMW/openmw/pull/2787 got merged --- docs/source/reference/modding/paths.rst | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index ea9b6c416..97cfe37a5 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -43,16 +43,16 @@ Savegames Screenshots ----------- -+--------------+-----------------------------------------------------------------------------------------------+ -| OS | Location | -+==============+===============================================================================================+ -| Linux | ``$HOME/.local/share/openmw`` | -+--------------+-----------------------------------------------------------------------------------------------+ -| Mac | ``$HOME/Library/Application\ Support/openmw`` | -+--------------+---------------+-------------------------------------------------------------------------------+ -| Windows | File Explorer | ``Documents\My Games\OpenMW`` | -| | | | -| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW"`` | -| | | | -| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` | -+--------------+---------------+-------------------------------------------------------------------------------+ ++--------------+-----------------------------------------------------------------------------------------------------------+ +| OS | Location | ++==============+===========================================================================================================+ +| Linux | ``$HOME/.local/share/openmw/screenshots`` | ++--------------+-----------------------------------------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Application\ Support/openmw/screenshots`` | ++--------------+---------------+-------------------------------------------------------------------------------------------+ +| Windows | File Explorer | ``Documents\My Games\OpenMW\screenshots`` | +| | | | +| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW\screenshots"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\screenshots`` | ++--------------+---------------+-------------------------------------------------------------------------------------------+ From e2a603f6e849f04c03fec0044d191ce36b83e64a Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 6 Oct 2020 09:08:49 +0200 Subject: [PATCH 23/66] Remove blank space --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e7ea6800..cd920b456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -760,9 +760,7 @@ elseif(NOT APPLE) 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/DejaVu Font License.txt" - DESTINATION ".") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".") From 683cf8cad5d49f96124678aed701cab40737dff1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 6 Oct 2020 16:56:55 +0000 Subject: [PATCH 24/66] Remove incorrect spaces from filename --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd920b456..8b6836237 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -760,7 +760,7 @@ elseif(NOT APPLE) 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/DejaVu Font License.txt" DESTINATION ".") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".") From e7c37f21b7a90bd89a67b5cae95fb62ce1c3ac5f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 6 Oct 2020 20:24:05 +0300 Subject: [PATCH 25/66] Add NiFloatInterpController abstraction --- components/nif/controller.cpp | 16 ++-------------- components/nif/controller.hpp | 13 +++---------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 8d943b58a..07699239e 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -170,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); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index bf043fbdb..a527a4400 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -143,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 { From 0d02a3392a801c1367ae3d68337c0e2a7739f9e6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 6 Oct 2020 20:36:10 +0300 Subject: [PATCH 26/66] Clean up --- components/nif/data.hpp | 2 -- components/nif/niffile.cpp | 1 - components/nif/property.hpp | 1 - 3 files changed, 4 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 1519bd1b3..58d6cf755 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -75,8 +75,6 @@ class NiAutoNormalParticlesData : public NiGeometryData public: int numParticles{0}; - float particleRadius; - int activeCount; std::vector particleRadii, sizes, rotationAngles; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 31c959964..550b5fafc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -108,7 +108,6 @@ static std::map makeFactory() return factory; } - ///Make the factory map used for parsing the file static const std::map factories = makeFactory(); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index e20d948f0..aeef0c027 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -162,7 +162,6 @@ struct NiWireframeProperty : public Property } }; - // The rest are all struct-based template struct StructPropT : Property From 302d8eed274351aaf09fc115868b8d17d1c5c8d3 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 6 Oct 2020 20:47:06 +0300 Subject: [PATCH 27/66] Clean up Roll- and FlipController implementation --- components/nifosg/controller.cpp | 13 +------------ components/nifosg/controller.hpp | 10 +++++----- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index b6610728a..0f4c4a5bd 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -310,11 +310,6 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) RollController::RollController(const Nif::NiFloatData *data) : mData(data->mKeyList, 1.f) - , mStartingTime(0) -{ -} - -RollController::RollController() : mStartingTime(0) { } @@ -322,7 +317,7 @@ RollController::RollController(const RollController ©, const osg::CopyOp &co : osg::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) - , mStartingTime(0) + , mStartingTime(copy.mStartingTime) { } @@ -462,12 +457,6 @@ FlipController::FlipController(int texSlot, float delta, const std::vector > mTextures; public: FlipController(const Nif::NiFlipController* ctrl, const std::vector >& textures); FlipController(int texSlot, float delta, const std::vector >& textures); - FlipController(); + FlipController() = default; FlipController(const FlipController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, FlipController) From df1014303d5c2a2e2006f5f0753602bae3df0ee1 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 6 Oct 2020 20:52:21 +0300 Subject: [PATCH 28/66] Add more generic extra data NIF types --- components/nif/extra.cpp | 50 ++++++++++++++++++++++++++++++++++++++ components/nif/extra.hpp | 49 +++++++++++++++++++++++++++++++++++++ components/nif/niffile.cpp | 8 ++++++ components/nif/record.hpp | 10 +++++++- 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index cb654d5a0..d08e5d738 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -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); +} } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index d935add55..0e8cc16bf 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -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 data; + + void read(NIFStream *nif); +}; + +struct NiBinaryExtraData : public Extra +{ + std::vector 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 data; + + void read(NIFStream *nif); +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 550b5fafc..8d65753d2 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -105,6 +105,14 @@ static std::map makeFactory() factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; factory["NiLookAtController"] = {&construct , RC_NiLookAtController }; factory["NiPalette"] = {&construct , RC_NiPalette }; + factory["NiIntegerExtraData"] = {&construct , RC_NiIntegerExtraData }; + factory["NiIntegersExtraData"] = {&construct , RC_NiIntegersExtraData }; + factory["NiBinaryExtraData"] = {&construct , RC_NiBinaryExtraData }; + factory["NiBooleanExtraData"] = {&construct , RC_NiBooleanExtraData }; + factory["NiVectorExtraData"] = {&construct , RC_NiVectorExtraData }; + factory["NiColorExtraData"] = {&construct , RC_NiColorExtraData }; + factory["NiFloatExtraData"] = {&construct , RC_NiFloatExtraData }; + factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 67202d2fe..f9bb613a0 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -101,7 +101,15 @@ enum RecordType RC_RootCollisionNode, RC_NiSphericalCollider, RC_NiLookAtController, - RC_NiPalette + RC_NiPalette, + RC_NiIntegerExtraData, + RC_NiIntegersExtraData, + RC_NiBinaryExtraData, + RC_NiBooleanExtraData, + RC_NiVectorExtraData, + RC_NiColorExtraData, + RC_NiFloatExtraData, + RC_NiFloatsExtraData }; /// Base class for all records From c34fd220db1f8002bad29078dee34f0cc0b4e046 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 7 Oct 2020 17:31:29 +0000 Subject: [PATCH 29/66] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2cb111a1a..27cc6d815 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,9 +59,6 @@ Debian_Clang_tests: 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: From a47d96ce0b06354f7b560b8d031cd507dfa0acf5 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 7 Oct 2020 19:06:28 +0000 Subject: [PATCH 30/66] Install CMake on MacOS if it's missing --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27cc6d815..a04e347c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,7 +60,8 @@ MacOS: tags: - macos stage: build - allow_failure: true + before_script: + - command -v cmake >/dev/null 2>&1 || brew install cmake script: - rm -fr build/* # remove anything in the build directory - CI/before_install.osx.sh From cfd9268a74ea088b4d34fef8dbebaed57f9768d3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 7 Oct 2020 19:12:13 +0000 Subject: [PATCH 31/66] Install Qt if missing --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a04e347c4..8f53c6942 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,6 +62,7 @@ MacOS: stage: build before_script: - command -v cmake >/dev/null 2>&1 || brew install cmake + - brew list qt || brew install qt script: - rm -fr build/* # remove anything in the build directory - CI/before_install.osx.sh From 63a6541d0eff2932770e3de9a67b2a0b25e76278 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 7 Oct 2020 22:11:05 +0200 Subject: [PATCH 32/66] Use separate caches for Debian builds Different builds cache different states. Each time cache file is written it erases the state created by a different type of build. --- .gitlab-ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2cb111a1a..0855633af 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,6 @@ stages: - linux image: debian:bullseye cache: - key: cache.002 paths: - apt-cache/ - ccache/ @@ -32,12 +31,16 @@ stages: Debian_GCC: extends: .Debian + cache: + key: Debian_GCC.v1 variables: CC: gcc CXX: g++ Debian_GCC_tests: extends: .Debian + cache: + key: Debian_GCC_tests.v1 variables: CC: gcc CXX: g++ @@ -45,12 +48,16 @@ Debian_GCC_tests: Debian_Clang: extends: .Debian + cache: + key: Debian_Clang.v1 variables: CC: clang CXX: clang++ Debian_Clang_tests: extends: .Debian + cache: + key: Debian_Clang_tests.v1 variables: CC: clang CXX: clang++ From 5515bc94b7b95b167e84c4b13fad6a8a3740af52 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 7 Oct 2020 21:04:36 +0000 Subject: [PATCH 33/66] Keep logs as artifacts --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8f53c6942..77650aea5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,6 +71,15 @@ MacOS: artifacts: paths: - build/OpenMW-*.dmg + - "*.log" + - build/*.log + - build/*/*.log + - build/*/*/*.log + - build/*/*/*/*.log + - build/*/*/*/*/*.log + - build/*/*/*/*/*/*.log + - build/*/*/*/*/*/*/*.log + - build/*/*/*/*/*/*/*/*.log variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" From 6be808e3016d8b17865c43b065c89e2b59c8d569 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 7 Oct 2020 22:44:09 +0300 Subject: [PATCH 34/66] RigGeometry: convert some pairs to structs --- components/nifosg/nifloader.cpp | 8 ++--- components/sceneutil/riggeometry.cpp | 35 +++++++------------ components/sceneutil/riggeometry.hpp | 50 ++++++++++++++++++++++------ 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 61a276dc1..4a2dac36c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1239,15 +1239,13 @@ namespace NifOsg std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); SceneUtil::RigGeometry::BoneInfluence influence; - const std::vector &weights = data->bones[i].weights; + const auto& weights = data->bones[i].weights; for(size_t j = 0;j < weights.size();j++) - { - influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight); - } + influence.mWeights.push_back({weights[j].vertex, weights[j].weight}); influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); - map->mData.emplace_back(boneName, influence); + map->mData.push_back({boneName, influence}); } rig->setInfluenceMap(map); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index b9201fdf6..c6ad7e9de 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -137,14 +137,13 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) } mBoneNodesVector.clear(); - for (auto& bonePair : mBoneSphereVector->mData) + for (auto& boundPair : mBoneSphereVector->mData) { - const std::string& boneName = bonePair.first; - Bone* bone = mSkeleton->getBone(boneName); + Bone* bone = mSkeleton->getBone(boundPair.name); if (!bone) { mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << boundPair.name; continue; } @@ -155,12 +154,11 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) { for (auto &weight : pair.first) { - const std::string& boneName = weight.first.first; - Bone* bone = mSkeleton->getBone(boneName); + Bone* bone = mSkeleton->getBone(weight.boneName); if (!bone) { mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << weight.boneName; continue; } @@ -218,7 +216,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if (bone == nullptr) continue; - accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat); + accumulateMatrix(weight.bindMatrix, bone->mMatrixInSkeletonSpace, weight.value, resultMat); index++; } @@ -281,7 +279,7 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) continue; index++; - osg::BoundingSpheref bs = boundPair.second; + osg::BoundingSpheref bs = boundPair.sphere; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); else @@ -337,30 +335,21 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) { mInfluenceMap = influenceMap; - typedef std::map > Vertex2BoneMap; + using Vertex2BoneMap = std::map>; Vertex2BoneMap vertex2BoneMap; mBoneSphereVector = new BoneSphereVector; mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size()); mBone2VertexVector = new Bone2VertexVector; - for (auto& influencePair : mInfluenceMap->mData) + for (const BoneData& bone : mInfluenceMap->mData) { - const std::string& boneName = influencePair.first; - const BoneInfluence& bi = influencePair.second; - mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere); - - for (auto& weightPair: bi.mWeights) - { - std::vector& vec = vertex2BoneMap[weightPair.first]; - - vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second); - } + mBoneSphereVector->mData.push_back({bone.name, bone.influence.mBoundSphere}); + for (auto& weight : bone.influence.mWeights) + vertex2BoneMap[weight.vertex].push_back({bone.name, bone.influence.mInvBindMatrix, weight.value}); } Bone2VertexMap bone2VertexMap; for (auto& vertexPair : vertex2BoneMap) - { bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first); - } mBone2VertexVector->mData.reserve(bone2VertexMap.size()); mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end()); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 801c172b3..5f1decf5f 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -25,17 +25,32 @@ namespace SceneUtil // Currently empty as this is difficult to implement. Technically we would need to compile both internal geometries in separate frames but this method is only called once. Alternatively we could compile just the static parts of the model. virtual void compileGLObjects(osg::RenderInfo& renderInfo) const {} + struct VertexWeight + { + unsigned short vertex; + float value; + }; + struct BoneInfluence { osg::Matrixf mInvBindMatrix; osg::BoundingSpheref mBoundSphere; - // - std::vector> mWeights; + std::vector mWeights; + }; + + struct BoneData + { + std::string name; + BoneInfluence influence; + bool operator<(const BoneData& other) const + { + return name < other.name; + } }; struct InfluenceMap : public osg::Referenced { - std::vector> mData; + std::vector mData; }; void setInfluenceMap(osg::ref_ptr influenceMap); @@ -79,23 +94,36 @@ namespace SceneUtil osg::ref_ptr mInfluenceMap; - typedef std::pair BoneBindMatrixPair; + struct BoneWeight + { + std::string boneName; + osg::Matrixf bindMatrix; + float value; + bool operator<(const BoneWeight& other) const + { + return boneName < other.boneName; + } + }; - typedef std::pair BoneWeight; - - typedef std::vector VertexList; - - typedef std::map, VertexList> Bone2VertexMap; + using VertexList = std::vector; + using BoneWeightList = std::vector; + using Bone2VertexMap = std::map; struct Bone2VertexVector : public osg::Referenced { - std::vector, VertexList>> mData; + std::vector> mData; }; osg::ref_ptr mBone2VertexVector; + struct BoneSphere + { + std::string name; + osg::BoundingSpheref sphere; + }; + struct BoneSphereVector : public osg::Referenced { - std::vector> mData; + std::vector mData; }; osg::ref_ptr mBoneSphereVector; std::vector mBoneNodesVector; From 7d73e73cad545f1ae05536ca563baf394769ab45 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 7 Oct 2020 23:47:59 +0200 Subject: [PATCH 35/66] Consider tile as not removed when it is not found --- components/detournavigator/navmeshcacheitem.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index f13341397..d4d6418dc 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -143,7 +143,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 +181,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)); } }; From eb140ed15fea0369be15705143d1a4669142e0cd Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 7 Oct 2020 23:54:32 +0200 Subject: [PATCH 36/66] Write unknown status numeric value --- components/detournavigator/asyncnavmeshupdater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 0683a43bc..7af3e0967 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -39,7 +39,7 @@ namespace DetourNavigator case UpdateNavMeshStatus::lost: return stream << "lost"; } - return stream << "unknown"; + return stream << "unknown(" << static_cast(value) << ")"; } AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, From f637dc38bd15414716669a46e7fe09c76025f324 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 8 Oct 2020 00:04:51 +0200 Subject: [PATCH 37/66] Add cached flag to update navmesh status --- .../detournavigator/asyncnavmeshupdater.cpp | 6 ++++++ components/detournavigator/makenavmesh.cpp | 6 +++++- components/detournavigator/navmeshcacheitem.hpp | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 7af3e0967..49339ebdf 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -38,6 +38,12 @@ 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(" << static_cast(value) << ")"; } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index beee95113..7c7dcf186 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -559,6 +559,7 @@ namespace DetourNavigator } auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); + bool cached = static_cast(cachedNavMeshData); if (!cachedNavMeshData) { @@ -584,6 +585,7 @@ namespace DetourNavigator { cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); + cached = static_cast(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(); } } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index d4d6418dc..76f74f266 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -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; From 7591d45008471d89bb2e4452221a86b2c09de518 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 8 Oct 2020 00:58:11 +0200 Subject: [PATCH 38/66] Use memcpy to create navmesh key Implementation with memcpy is ~13 times faster. --- .../detournavigator/navmeshtilescache.cpp | 64 ++++++++----------- .../detournavigator/navmeshtilescache.hpp | 31 ++++++--- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 466d2e708..f554cd414 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -9,42 +9,32 @@ namespace DetourNavigator { namespace { - inline std::string makeNavMeshKey(const RecastMesh& recastMesh, + inline std::vector makeNavMeshKey(const RecastMesh& recastMesh, const std::vector& 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(recastMesh.getIndices().data()), - reinterpret_cast(recastMesh.getIndices().data() + recastMesh.getIndices().size()), - std::back_inserter(result) - ); - std::copy( - reinterpret_cast(recastMesh.getVertices().data()), - reinterpret_cast(recastMesh.getVertices().data() + recastMesh.getVertices().size()), - std::back_inserter(result) - ); - std::copy( - reinterpret_cast(recastMesh.getAreaTypes().data()), - reinterpret_cast(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()), - std::back_inserter(result) - ); - std::copy( - reinterpret_cast(recastMesh.getWater().data()), - reinterpret_cast(recastMesh.getWater().data() + recastMesh.getWater().size()), - std::back_inserter(result) - ); - std::copy( - reinterpret_cast(offMeshConnections.data()), - reinterpret_cast(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 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 int operator ()(const std::vector& lhs) @@ -225,7 +215,7 @@ namespace DetourNavigator }; } - int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const + int NavMeshTilesCache::RecastMeshKeyView::compare(const std::vector& other) const { CompareBytes compareBytes {other.data(), other.data() + other.size()}; diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 57f57a56f..064d9e185 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include namespace osg { @@ -33,10 +35,10 @@ namespace DetourNavigator std::atomic mUseCount; osg::Vec3f mAgentHalfExtents; TilePosition mChangedTile; - std::string mNavMeshKey; + std::vector mNavMeshKey; NavMeshData mNavMeshData; - Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey) + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::vector&& 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& value) : mValue(&value) {} - const std::string& getValue() const + const std::vector& getValue() const { assert(mValue); return *mValue; } - virtual int compare(const std::string& other) const + virtual int compare(const std::vector& 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* mValue = nullptr; }; class RecastMeshKeyView : public KeyView @@ -156,7 +171,7 @@ namespace DetourNavigator RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} - int compare(const std::string& other) const override; + int compare(const std::vector& other) const override; bool isLess(const KeyView& other) const override { From 02861fa8e3a3d98d8354a83cdb9cedb4a55dc62d Mon Sep 17 00:00:00 2001 From: tessa Date: Thu, 8 Oct 2020 13:06:04 -0500 Subject: [PATCH 39/66] fix cmake warning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b6836237..4918e31fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -882,7 +882,7 @@ elseif(NOT APPLE) INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") endif(WIN32) -endif(NOT APPLE) +endif() # Doxygen Target -- simply run 'make doc' or 'make doc_pages' # output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen" From 38e567a7e4eb282c22c2ca4c1b99d7e4e7e0a7cc Mon Sep 17 00:00:00 2001 From: tess <2687892-TescoShoppah@users.noreply.gitlab.com> Date: Thu, 8 Oct 2020 19:34:03 +0000 Subject: [PATCH 40/66] match opening and closing if args --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4918e31fd..fecb589eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -882,7 +882,7 @@ elseif(NOT APPLE) INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") endif(WIN32) -endif() +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" From ec825b2510131d747b143e15cda4a2ca25d0824f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 8 Oct 2020 22:44:46 +0300 Subject: [PATCH 41/66] Fix RayCastingResult warning --- apps/openmw/mwphysics/physicssystem.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8b07fea4b..07859c1e2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -265,7 +265,11 @@ namespace MWPhysics RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { if (from == to) - return RayCastingResult { false }; + { + RayCastingResult result; + result.mHit = false; + return result; + } btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); From 2f1b7c4d2ff823ac6b2b490567429be64338054c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 8 Oct 2020 21:35:37 +0000 Subject: [PATCH 42/66] Rename DMG file --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 77650aea5..d604e90c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,6 +68,7 @@ MacOS: - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j2 package + - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done artifacts: paths: - build/OpenMW-*.dmg From 3a39e1f8f8fd1dacf5c28a2d558c8dc8bc05f0d6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 8 Oct 2020 22:25:16 +0000 Subject: [PATCH 43/66] config was from Windows CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d604e90c8..d335dfd91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,7 +68,7 @@ MacOS: - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j2 package - - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done + - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done artifacts: paths: - build/OpenMW-*.dmg From 9f08dc99687eaf175ec2ff5f695dfae0decf04a3 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 8 Oct 2020 23:24:28 +0000 Subject: [PATCH 44/66] Revert "Merge branch 'skinning' into 'master'" This reverts merge request !327 --- components/nifosg/nifloader.cpp | 8 +++-- components/sceneutil/riggeometry.cpp | 35 ++++++++++++------- components/sceneutil/riggeometry.hpp | 50 ++++++---------------------- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4a2dac36c..61a276dc1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1239,13 +1239,15 @@ namespace NifOsg std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); SceneUtil::RigGeometry::BoneInfluence influence; - const auto& weights = data->bones[i].weights; + const std::vector &weights = data->bones[i].weights; for(size_t j = 0;j < weights.size();j++) - influence.mWeights.push_back({weights[j].vertex, weights[j].weight}); + { + influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight); + } influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); - map->mData.push_back({boneName, influence}); + map->mData.emplace_back(boneName, influence); } rig->setInfluenceMap(map); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index c6ad7e9de..b9201fdf6 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -137,13 +137,14 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) } mBoneNodesVector.clear(); - for (auto& boundPair : mBoneSphereVector->mData) + for (auto& bonePair : mBoneSphereVector->mData) { - Bone* bone = mSkeleton->getBone(boundPair.name); + const std::string& boneName = bonePair.first; + Bone* bone = mSkeleton->getBone(boneName); if (!bone) { mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << boundPair.name; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; continue; } @@ -154,11 +155,12 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) { for (auto &weight : pair.first) { - Bone* bone = mSkeleton->getBone(weight.boneName); + const std::string& boneName = weight.first.first; + Bone* bone = mSkeleton->getBone(boneName); if (!bone) { mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << weight.boneName; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; continue; } @@ -216,7 +218,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if (bone == nullptr) continue; - accumulateMatrix(weight.bindMatrix, bone->mMatrixInSkeletonSpace, weight.value, resultMat); + accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat); index++; } @@ -279,7 +281,7 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) continue; index++; - osg::BoundingSpheref bs = boundPair.sphere; + osg::BoundingSpheref bs = boundPair.second; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); else @@ -335,21 +337,30 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) { mInfluenceMap = influenceMap; - using Vertex2BoneMap = std::map>; + typedef std::map > Vertex2BoneMap; Vertex2BoneMap vertex2BoneMap; mBoneSphereVector = new BoneSphereVector; mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size()); mBone2VertexVector = new Bone2VertexVector; - for (const BoneData& bone : mInfluenceMap->mData) + for (auto& influencePair : mInfluenceMap->mData) { - mBoneSphereVector->mData.push_back({bone.name, bone.influence.mBoundSphere}); - for (auto& weight : bone.influence.mWeights) - vertex2BoneMap[weight.vertex].push_back({bone.name, bone.influence.mInvBindMatrix, weight.value}); + const std::string& boneName = influencePair.first; + const BoneInfluence& bi = influencePair.second; + mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere); + + for (auto& weightPair: bi.mWeights) + { + std::vector& vec = vertex2BoneMap[weightPair.first]; + + vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second); + } } Bone2VertexMap bone2VertexMap; for (auto& vertexPair : vertex2BoneMap) + { bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first); + } mBone2VertexVector->mData.reserve(bone2VertexMap.size()); mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end()); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 5f1decf5f..801c172b3 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -25,32 +25,17 @@ namespace SceneUtil // Currently empty as this is difficult to implement. Technically we would need to compile both internal geometries in separate frames but this method is only called once. Alternatively we could compile just the static parts of the model. virtual void compileGLObjects(osg::RenderInfo& renderInfo) const {} - struct VertexWeight - { - unsigned short vertex; - float value; - }; - struct BoneInfluence { osg::Matrixf mInvBindMatrix; osg::BoundingSpheref mBoundSphere; - std::vector mWeights; - }; - - struct BoneData - { - std::string name; - BoneInfluence influence; - bool operator<(const BoneData& other) const - { - return name < other.name; - } + // + std::vector> mWeights; }; struct InfluenceMap : public osg::Referenced { - std::vector mData; + std::vector> mData; }; void setInfluenceMap(osg::ref_ptr influenceMap); @@ -94,36 +79,23 @@ namespace SceneUtil osg::ref_ptr mInfluenceMap; - struct BoneWeight - { - std::string boneName; - osg::Matrixf bindMatrix; - float value; - bool operator<(const BoneWeight& other) const - { - return boneName < other.boneName; - } - }; + typedef std::pair BoneBindMatrixPair; - using VertexList = std::vector; - using BoneWeightList = std::vector; - using Bone2VertexMap = std::map; + typedef std::pair BoneWeight; + + typedef std::vector VertexList; + + typedef std::map, VertexList> Bone2VertexMap; struct Bone2VertexVector : public osg::Referenced { - std::vector> mData; + std::vector, VertexList>> mData; }; osg::ref_ptr mBone2VertexVector; - struct BoneSphere - { - std::string name; - osg::BoundingSpheref sphere; - }; - struct BoneSphereVector : public osg::Referenced { - std::vector mData; + std::vector> mData; }; osg::ref_ptr mBoneSphereVector; std::vector mBoneNodesVector; From 1aa5e5cc52bb5bc18c204a7043cfdf906bba9275 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 9 Oct 2020 13:32:28 +0000 Subject: [PATCH 45/66] Increase CCache size for Debian where needed --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0855633af..fb4263ee2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ stages: script: - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - - ccache -z -M 1G + - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - cd build - cmake --build . -- -j $(nproc) @@ -36,6 +36,7 @@ Debian_GCC: variables: CC: gcc CXX: g++ + CCACHE_SIZE: 3G Debian_GCC_tests: extends: .Debian @@ -44,6 +45,7 @@ Debian_GCC_tests: variables: CC: gcc CXX: g++ + CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 Debian_Clang: @@ -53,6 +55,7 @@ Debian_Clang: variables: CC: clang CXX: clang++ + CCACHE_SIZE: 2G Debian_Clang_tests: extends: .Debian @@ -61,6 +64,7 @@ Debian_Clang_tests: variables: CC: clang CXX: clang++ + CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 MacOS: From ef41edba9b6945ad50c8a3509d6bf0ddbf31b065 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 9 Oct 2020 13:56:21 +0000 Subject: [PATCH 46/66] Install tools in before_install.osx.sh --- .gitlab-ci.yml | 13 +------------ CI/before_install.osx.sh | 5 ++++- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d335dfd91..8edf66569 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,9 +60,6 @@ MacOS: tags: - macos stage: build - before_script: - - command -v cmake >/dev/null 2>&1 || brew install cmake - - brew list qt || brew install qt script: - rm -fr build/* # remove anything in the build directory - CI/before_install.osx.sh @@ -72,15 +69,7 @@ MacOS: artifacts: paths: - build/OpenMW-*.dmg - - "*.log" - - build/*.log - - build/*/*.log - - build/*/*/*.log - - build/*/*/*/*.log - - build/*/*/*/*/*.log - - build/*/*/*/*/*/*.log - - build/*/*/*/*/*/*/*.log - - build/*/*/*/*/*/*/*/*.log + - "build/**/*.log" variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 5dae1f102..c3514e25b 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -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 From 0dcb8c6b699d3737f8a3ad2641fd42f75b2be41e Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 9 Oct 2020 19:20:50 +0300 Subject: [PATCH 47/66] Fix pick/probe uses decrement --- apps/openmw/mwmechanics/security.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 5b79d821c..e642a7bb4 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -65,7 +65,7 @@ namespace MWMechanics resultMessage = "#{sLockFail}"; } - lockpick.getCellRef().setCharge(uses-1); + lockpick.getCellRef().setCharge(--uses); if (!uses) lockpick.getContainerStore()->remove(lockpick, 1, mActor); } @@ -110,7 +110,7 @@ namespace MWMechanics resultMessage = "#{sTrapFail}"; } - probe.getCellRef().setCharge(uses-1); + probe.getCellRef().setCharge(--uses); if (!uses) probe.getContainerStore()->remove(probe, 1, mActor); } From 05cd005b30e911a74af9126b1ad09ac88b1be0d5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 9 Oct 2020 19:37:54 +0300 Subject: [PATCH 48/66] Fix NiTriStripsData loading --- components/nif/data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index e76541d5c..91b3beba4 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -145,7 +145,7 @@ void NiTriStripsData::read(NIFStream *nif) nif->getUShorts(lengths, numStrips); // "Has Strips" flag. Exceptionally useful. - bool hasStrips = false; + bool hasStrips = true; if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) hasStrips = nif->getBoolean(); if (!hasStrips || !numStrips) From 39678c74bfd073e9616b4af159ddea9983437c36 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 9 Oct 2020 20:34:36 +0300 Subject: [PATCH 49/66] Add many more godmode checks to harmful magic (bug #5633) --- apps/openmw/mwclass/npc.cpp | 6 ++-- apps/openmw/mwgui/inventorywindow.cpp | 3 +- apps/openmw/mwgui/quickkeysmenu.cpp | 3 +- apps/openmw/mwgui/spellwindow.cpp | 3 +- apps/openmw/mwmechanics/actors.cpp | 34 +++++++++++++++------ apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/tickableeffects.cpp | 31 ++++++++++++++----- apps/openmw/mwworld/player.cpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 7 +++-- 9 files changed, 67 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4aab95f9d..f4a711432 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -941,7 +941,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(); @@ -990,7 +991,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(ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index d11ee4f0c..b0749d4bd 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -778,7 +778,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; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 8449e6a5b..214e52942 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -340,7 +340,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) { diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 7776b376a..d76a59820 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -241,8 +241,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(), "")); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3de1a3acc..b12518b7b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -918,6 +918,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(); @@ -969,8 +970,11 @@ namespace MWMechanics for (int i = 0; i < 3; ++i) { DynamicStat 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); @@ -982,9 +986,14 @@ namespace MWMechanics for(int i = 0;i < ESM::Attribute::Length;++i) { AttributeValue stat = creatureStats.getAttribute(i); - stat.setModifier(static_cast(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(fortify - drain - absorb)); creatureStats.setAttribute(i, stat); } @@ -1214,14 +1223,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(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(fortify - drain - absorb)); } } @@ -1827,6 +1842,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) @@ -2007,7 +2023,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 diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1dcccb888..ad64f79d7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1929,7 +1929,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); diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp index 31e8c150c..fa3b6ac20 100644 --- a/apps/openmw/mwmechanics/tickableeffects.cpp +++ b/apps/openmw/mwmechanics/tickableeffects.cpp @@ -68,11 +68,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); @@ -91,6 +94,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; @@ -98,25 +103,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 (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - + 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: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); + if (!godmode || magnitude <= 0) + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); break; case ESM::MagicEffect::DisintegrateArmor: { + if (godmode) + break; static const std::array priorities { MWWorld::InventoryStore::Slot_CarriedLeft, @@ -138,13 +150,14 @@ namespace MWMechanics break; } case ESM::MagicEffect::DisintegrateWeapon: - disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); + 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))); @@ -169,6 +182,8 @@ namespace MWMechanics case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::Poison: { + if (godmode) + break; adjustDynamicStat(creatureStats, 0, -magnitude); receivedMagicDamage = true; break; @@ -179,6 +194,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) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 2b157f18a..66ae4e319 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -230,7 +230,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(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 265a7663c..127fbb45a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1863,10 +1863,13 @@ namespace MWWorld else mRendering->getCamera()->setSneakOffset(0.f); - int blind = static_cast(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(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); - int nightEye = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude()); + int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); } From b85875a3542d152e7b9f719f697b771af12d336a Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 9 Oct 2020 21:30:24 +0300 Subject: [PATCH 50/66] Ignore Burden effect in god mode --- apps/openmw/mwclass/actor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 61d6e7347..33aeb26bb 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -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,7 +80,8 @@ namespace MWClass float weight = getContainerStore(ptr).getWeight(); const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); - weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); + if (ptr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->getGodModeState()) + weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); return (weight < 0) ? 0.0f : weight; } From c6eb0dacd367d53035ee19374b0c2a2b95f68ece Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 9 Oct 2020 20:51:25 +0000 Subject: [PATCH 51/66] Bump Debian cache version numbers This should stop MR builds clobbering the increased size with their tiny size. --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fb4263ee2..75488b6b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,7 @@ stages: Debian_GCC: extends: .Debian cache: - key: Debian_GCC.v1 + key: Debian_GCC.v2 variables: CC: gcc CXX: g++ @@ -41,7 +41,7 @@ Debian_GCC: Debian_GCC_tests: extends: .Debian cache: - key: Debian_GCC_tests.v1 + key: Debian_GCC_tests.v2 variables: CC: gcc CXX: g++ @@ -51,7 +51,7 @@ Debian_GCC_tests: Debian_Clang: extends: .Debian cache: - key: Debian_Clang.v1 + key: Debian_Clang.v2 variables: CC: clang CXX: clang++ @@ -60,7 +60,7 @@ Debian_Clang: Debian_Clang_tests: extends: .Debian cache: - key: Debian_Clang_tests.v1 + key: Debian_Clang_tests.v2 variables: CC: clang CXX: clang++ From 460e69e92a74f2c2ebab9cf03e40e106afb8b6a2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 10 Oct 2020 12:08:27 +0200 Subject: [PATCH 52/66] Get rid of warning: dynamic exception specifications are deprecated --- components/compiler/exception.hpp | 14 +++++++------- components/detournavigator/asyncnavmeshupdater.cpp | 2 +- components/detournavigator/asyncnavmeshupdater.hpp | 2 +- components/detournavigator/objectid.hpp | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/compiler/exception.hpp b/components/compiler/exception.hpp index 33bad7590..f21f2e586 100644 --- a/components/compiler/exception.hpp +++ b/components/compiler/exception.hpp @@ -10,8 +10,8 @@ namespace Compiler class SourceException : public std::exception { public: - - virtual const char *what() const throw() { return "Compile error";} + + const char *what() const noexcept override { return "Compile error";} ///< Return error message }; @@ -20,18 +20,18 @@ namespace Compiler class FileException : public SourceException { 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 }; /// \brief Exception: EOF condition encountered class EOFException : public SourceException - { + { public: - - virtual const char *what() const throw() { return "End of file"; } + + const char *what() const noexcept final { return "End of file"; } ///< Return error message }; } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 0683a43bc..6279d68b7 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -126,7 +126,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) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 4debcd6cd..26f886512 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -113,7 +113,7 @@ namespace DetourNavigator std::map mThreadsQueues; std::vector mThreads; - void process() throw(); + void process() noexcept; bool processJob(const Job& job); diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp index 6ddcc9169..9c4b5b271 100644 --- a/components/detournavigator/objectid.hpp +++ b/components/detournavigator/objectid.hpp @@ -10,22 +10,22 @@ namespace DetourNavigator { public: template - explicit ObjectId(T* value) throw() + explicit ObjectId(T* value) noexcept : mValue(reinterpret_cast(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 { - std::size_t operator ()(const DetourNavigator::ObjectId value) const throw() + std::size_t operator ()(const DetourNavigator::ObjectId value) const noexcept { return value.value(); } From 1842f546c3fca214828ada0bd7fe147692de5442 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 10 Oct 2020 14:27:52 +0300 Subject: [PATCH 53/66] Restore empty target check in spell absorption --- apps/openmw/mwmechanics/spellabsorption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index 71e1d0aee..74df66780 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -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); From 15078f5b3c0ca8c02989462662aec468993cdeff Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 10 Oct 2020 23:06:43 +0200 Subject: [PATCH 54/66] Fix #5630 "NPCs momentarily turn when the player character is moving backwards during combat" --- apps/openmw/mwmechanics/aicombat.cpp | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index cbe161af1..b98d5ef49 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -240,24 +240,24 @@ namespace MWMechanics storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); + if (isRangedCombat) + { + // rotate actor taking into account target movement direction and projectile speed + osg::Vec3f& lastTargetPos = storage.mLastTargetPos; + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); + lastTargetPos = vTargetPos; + + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); + } + else + { + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated + } + if (storage.mReadyToAttack) { - if (isRangedCombat) - { - // rotate actor taking into account target movement direction and projectile speed - osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); - lastTargetPos = vTargetPos; - - storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); - storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); - } - else - { - storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); - storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated - } - storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); From 3cadc16f88ed010afdf9fac1adadc7aabbbcc484 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 13 Oct 2020 09:59:02 +0200 Subject: [PATCH 55/66] Convert components to an OBJECT library. It saves space in the object directory (about 1G on an optimized build on Windows and BSD). Build should run somewhat fast as well. --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7f44cf6fb..6e0f4a65f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -212,7 +212,7 @@ endif () include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +add_library(components OBJECT ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components ${Boost_SYSTEM_LIBRARY} From 72549651e0b3afcbf2a481b8db2731281b760e41 Mon Sep 17 00:00:00 2001 From: Assumeru Date: Tue, 13 Oct 2020 17:46:32 +0200 Subject: [PATCH 56/66] Rework container resolution (#3006) * Rework container resolution * add optional argument to getCount * remove now-redundant changes * undo worldimp changes * move save-fixing code to InventoryState * replace Rng instances with Seeds --- CHANGELOG.md | 3 + apps/openmw/mwclass/container.cpp | 83 ++---- apps/openmw/mwclass/container.hpp | 25 +- apps/openmw/mwclass/creature.cpp | 8 - apps/openmw/mwclass/creature.hpp | 2 - apps/openmw/mwclass/npc.cpp | 8 - apps/openmw/mwclass/npc.hpp | 2 - apps/openmw/mwgui/container.cpp | 1 + apps/openmw/mwgui/containeritemmodel.cpp | 49 +-- apps/openmw/mwgui/containeritemmodel.hpp | 9 +- apps/openmw/mwgui/tradeitemmodel.cpp | 2 +- apps/openmw/mwgui/tradeitemmodel.hpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 35 +-- apps/openmw/mwgui/tradewindow.hpp | 3 +- apps/openmw/mwmechanics/levelledlist.hpp | 10 +- .../mwmechanics/mechanicsmanagerimp.cpp | 3 +- apps/openmw/mwworld/actionharvest.cpp | 1 + apps/openmw/mwworld/cellstore.cpp | 3 +- apps/openmw/mwworld/class.hpp | 2 - apps/openmw/mwworld/containerstore.cpp | 278 +++++++++--------- apps/openmw/mwworld/containerstore.hpp | 63 +++- apps/openmw/mwworld/inventorystore.cpp | 9 +- apps/openmw/mwworld/localscripts.cpp | 2 +- apps/openmw/mwworld/refdata.cpp | 4 +- apps/openmw/mwworld/refdata.hpp | 2 +- .../findrandompointaroundcircle.cpp | 2 +- components/esm/inventorystate.cpp | 15 + components/esm/savedgame.cpp | 2 +- components/misc/rng.cpp | 31 +- components/misc/rng.hpp | 20 +- 30 files changed, 371 insertions(+), 308 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9813cabff..80253e99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ 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 diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b4b068c91..807f2299b 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -31,44 +31,41 @@ namespace MWClass { - class ContainerCustomData : public MWWorld::CustomData + ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { - public: - MWWorld::ContainerStore mContainerStore; + unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max()); + // setting ownership not needed, since taking items from a container inherits the + // container's owner automatically + mStore.fillNonRandom(container.mInventory, "", seed); + } - virtual MWWorld::CustomData *clone() const; - - virtual ContainerCustomData& asContainerCustomData() - { - return *this; - } - virtual const ContainerCustomData& asContainerCustomData() const - { - return *this; - } - }; + ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory) + { + 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 data (new ContainerCustomData); - - MWWorld::LiveCellRef *ref = - ptr.get(); - - // setting ownership not needed, since taking items from a container inherits the - // container's owner automatically - data->mContainerStore.fill( - ref->mBase->mInventory, ""); + MWWorld::LiveCellRef *ref = ptr.get(); // store - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell()).release()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -98,17 +95,6 @@ namespace MWClass } } - void Container::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - 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()) { @@ -228,12 +214,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 @@ -253,8 +239,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; } @@ -317,28 +302,20 @@ 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 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(containerState.mInventory).release()); } void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - if (!ptr.getRefData().getCustomData()) + const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); + if (!ptr.getRefData().getCustomData() || !customData.mStore.isResolved()) { state.mHasCustomState = false; return; } - const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); ESM::ContainerState& containerState = state.asContainerState(); - customData.mContainerStore.writeState (containerState.mInventory); + customData.mStore.writeState (containerState.mInventory); } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index b84c5787b..0ee549f81 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -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; @@ -60,8 +83,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; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5c5524a12..8e67498a1 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -842,14 +842,6 @@ namespace MWClass } } - void Creature::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - 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 *ref = ptr.get(); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index ca991052b..21d25633a 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -123,8 +123,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; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f4a711432..0187f2727 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1401,14 +1401,6 @@ namespace MWClass } } - void Npc::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - 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 *ref = ptr.get(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 1ed4e8cae..43340b8d7 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -158,8 +158,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; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index a68fddca1..810a369d8 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -168,6 +168,7 @@ namespace MWGui if (!mPtr.isEmpty()) MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); + resetReference(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 0cfa6ebf5..31439f349 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -39,22 +39,29 @@ namespace namespace MWGui { - ContainerItemModel::ContainerItemModel(const std::vector& itemSources, const std::vector& 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(); @@ -62,7 +69,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) @@ -93,25 +100,31 @@ 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!"); - return *source.getClass().getContainerStore(source).add(item.mBase, count, source, allowAutoEquip); + return *store.add(item.mBase, count, source.first, allowAutoEquip); } 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) { if (stacks(*it, item.mBase)) { - 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); if (toRemove <= 0) return; } @@ -138,9 +151,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) { @@ -194,7 +207,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; @@ -224,7 +237,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()) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 806cc0a73..d09bd7def 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -1,8 +1,13 @@ #ifndef MWGUI_CONTAINER_ITEM_MODEL_H #define MWGUI_CONTAINER_ITEM_MODEL_H +#include +#include + #include "itemmodel.hpp" +#include "../mwworld/containerstore.hpp" + namespace MWGui { @@ -32,9 +37,9 @@ namespace MWGui virtual void update(); private: - std::vector mItemSources; + std::vector> mItemSources; std::vector mWorldItems; - + const bool mTrading; std::vector mItems; }; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index b1bab32dc..f5144ba81 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -119,7 +119,7 @@ namespace MWGui mBorrowedToUs.clear(); } - std::vector TradeItemModel::getItemsBorrowedToUs() + const std::vector TradeItemModel::getItemsBorrowedToUs() const { return mBorrowedToUs; } diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index cdb949c49..e349c225a 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -40,7 +40,7 @@ namespace MWGui /// and removing weight for items we've lent to someone else. void adjustEncumbrance (float& encumbrance); - std::vector getItemsBorrowedToUs(); + const std::vector getItemsBorrowedToUs() const; private: void borrowImpl(const ItemStack& item, std::vector& out); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 672ccbd06..e7dbd4447 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -99,20 +99,6 @@ namespace MWGui setCoord(400, 0, 400, 300); } - void TradeWindow::restock() - { - // Restock items on the actor inventory - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (MWWorld::Ptr& source : itemSources) - { - source.getClass().restock(source); - } - } - void TradeWindow::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; @@ -121,10 +107,10 @@ namespace MWGui mCurrentMerchantOffer = 0; std::vector 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 worldItems; MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); @@ -281,8 +267,8 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get(); // were there any items traded at all? - std::vector playerBought = playerItemModel->getItemsBorrowedToUs(); - std::vector merchantBought = mTradeModel->getItemsBorrowedToUs(); + const std::vector& playerBought = playerItemModel->getItemsBorrowedToUs(); + const std::vector& merchantBought = mTradeModel->getItemsBorrowedToUs(); if (playerBought.empty() && merchantBought.empty()) { // user notification @@ -313,7 +299,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)) { @@ -363,8 +349,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) @@ -478,7 +462,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 playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); + const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : playerBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); @@ -487,7 +471,7 @@ namespace MWGui merchantOffer -= std::max(cap, buyingPrice); } - std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); + const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : merchantBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); @@ -532,4 +516,9 @@ namespace MWGui mTradeModel = nullptr; mSortModel = nullptr; } + + void TradeWindow::onClose() + { + resetReference(); + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 0730df04f..b211c6d09 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -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(); }; } diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index f716f068d..c8368101a 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -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& 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 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()->mBase, false); + return getLevelledItem(ref.getPtr().get()->mBase, false, seed); else - return getLevelledItem(ref.getPtr().get()->mBase, true); + return getLevelledItem(ref.getPtr().get()->mBase, true, seed); } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 466a3bcc8..872a66799 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1045,6 +1045,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())); @@ -1065,7 +1066,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 diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index ff35938b2..c9468c715 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -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 takenMap; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 2cda83e17..14b96b27c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1030,7 +1030,8 @@ namespace MWWorld for (CellRefList::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); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 445f2e986..1b3d4029e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -353,8 +353,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; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 6337ca440..5ecb3dd3a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -23,6 +23,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 float getTotalWeight (const MWWorld::CellRefList& cellRefList) { @@ -44,6 +59,7 @@ namespace MWWorld::Ptr searchId (MWWorld::CellRefList& list, const std::string& id, MWWorld::ContainerStore *store) { + store->resolve(); std::string id2 = Misc::StringUtils::lowerCase (id); for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); @@ -61,6 +77,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()->mBase->mInventory, "", mStore.mSeed); + addScripts(mStore, mStore.mPtr.mCell); + mStore.mResolved = false; + } +} + template MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, const ESM::ObjectState& state) @@ -119,7 +147,11 @@ MWWorld::ContainerStore::ContainerStore() : mListener(nullptr) , mRechargingItemsUpToDate(false) , mCachedWeight (0) - , mWeightUpToDate (false) {} + , mWeightUpToDate (false) + , mModified(false) + , mResolved(false) + , mSeed() + , mPtr() {} MWWorld::ContainerStore::~ContainerStore() {} @@ -153,22 +185,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; } @@ -185,9 +207,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)); const std::string script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); @@ -199,6 +222,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) { @@ -216,7 +240,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; @@ -328,8 +352,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 = @@ -345,7 +371,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; } @@ -361,7 +387,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; @@ -439,6 +465,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) @@ -465,6 +492,7 @@ bool MWWorld::ContainerStore::hasVisibleItems() const int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) { assert(this == item.getContainerStore()); + resolve(); int toRemove = count; RefData& itemRef = item.getRefData(); @@ -476,7 +504,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; } @@ -490,20 +518,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::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 @@ -511,13 +552,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) @@ -526,137 +567,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()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i=0; i 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()->mBase, false); + std::string itemId = MWMechanics::getLevelledItem(ptr.get()->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, 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 allowedForReplace; - - //Check which lists need restocking: - for (std::map, 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::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::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::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().search(it->mItem)) - { - std::map::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() @@ -665,6 +612,45 @@ void MWWorld::ContainerStore::flagAsModified() mRechargingItemsUpToDate = false; } +bool MWWorld::ContainerStore::isResolved() const +{ + return mResolved; +} + +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()->mBase->mInventory, "", seed); + addScripts(*this, mPtr.mCell); + } + mModified = true; +} + +MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() +{ + if(mModified) + return {}; + std::shared_ptr listener = mResolutionListener.lock(); + if(!listener) + { + listener = std::make_shared(*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()->mBase->mInventory, "", seed); + addScripts(*this, mPtr.mCell); + } + return {listener}; +} + float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) @@ -761,6 +747,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()) @@ -836,6 +823,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(); @@ -853,13 +856,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::const_iterator @@ -893,9 +896,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) break; } } - - - mLevelledItemMap = inventory.mLevelledItemMap; } template diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f2858c5aa..c89e855f9 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -18,6 +19,8 @@ #include #include +#include + #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 ContainerStoreIterator; typedef ContainerStoreIteratorBase ConstContainerStoreIterator; + class ResolutionListener + { + ContainerStore& mStore; + public: + ResolutionListener(ContainerStore& store) : mStore(store) {} + ~ResolutionListener(); + }; + + class ResolutionHandle + { + std::shared_ptr mListener; + public: + ResolutionHandle(std::shared_ptr listener) : mListener(listener) {} + ResolutionHandle() {} + }; class ContainerStoreListener { @@ -93,15 +116,18 @@ namespace MWWorld MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - std::map, 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 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 ContainerStoreIterator getState (CellRefList& 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,15 @@ namespace MWWorld virtual void readState (const ESM::InventoryState& state); + bool isResolved() const; + + void resolve(); + ResolutionHandle resolveTemporarily(); + friend class ContainerStoreIteratorBase; friend class ContainerStoreIteratorBase; + friend class ResolutionListener; + friend class MWClass::Container; }; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d67a22884..cf2b97ce7 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -86,8 +86,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 @@ -850,8 +851,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; } } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index a727b4b3a..8a511030d 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -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) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f6fa3556f..a5f8ef4b1 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -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; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 6a59d9797..738a6d53a 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -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); diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index 3888c59fe..263dba68e 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -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(); diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index fe54762c5..2392d263f 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + 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 diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 0fc84e309..9edcb1a67 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -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) { diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 09279e85e..4189404b1 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -3,29 +3,44 @@ #include #include +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(0, 1 - std::numeric_limits::epsilon())(generator); + return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(sSeed.mGenerator); } - float Rng::rollClosedProbability() + float Rng::rollClosedProbability(Seed& seed) { - return std::uniform_real_distribution(0, 1)(generator); + return std::uniform_real_distribution(0, 1)(sSeed.mGenerator); } - int Rng::rollDice(int max) + int Rng::rollDice(int max, Seed& seed) { - return max > 0 ? std::uniform_int_distribution(0, max - 1)(generator) : 0; + return max > 0 ? std::uniform_int_distribution(0, max - 1)(sSeed.mGenerator) : 0; } unsigned int Rng::generateDefaultSeed() diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 65a554cf2..8efca438d 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -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(); From 4ea07639b87fc0787f55ec9717b4857f43e099c7 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 14 Oct 2020 06:15:23 +0200 Subject: [PATCH 57/66] Unbreak build with cmake < 3.12 This reverts commit 3cadc16f88ed010afdf9fac1adadc7aabbbcc484. --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6e0f4a65f..7f44cf6fb 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -212,7 +212,7 @@ endif () include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -add_library(components OBJECT ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components ${Boost_SYSTEM_LIBRARY} From 8e647aa72ae7e09eaa879039d8262a662de59f37 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 14 Oct 2020 09:16:01 +0400 Subject: [PATCH 58/66] Fix crash on saving --- apps/openmw/mwclass/container.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 807f2299b..9befa0636 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -308,8 +308,14 @@ namespace MWClass void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); - if (!ptr.getRefData().getCustomData() || !customData.mStore.isResolved()) + if (!customData.mStore.isResolved()) { state.mHasCustomState = false; return; From c3d84b2c7c8792c2d8c1687705905704ddf29149 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 Sep 2020 10:14:26 +0400 Subject: [PATCH 59/66] Ability to attach arrows to shooter's hands (feature #5642) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/weapontype.hpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 14 +++-- apps/openmw/mwrender/npcanimation.cpp | 13 +++-- docs/source/reference/modding/extended.rst | 60 ++++++++++++---------- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80253e99c..51a9c7deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Feature #5545: Option to allow stealing from an unconscious NPC during combat 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 diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp index 32e321d45..056a1dbfd 100644 --- a/apps/openmw/mwmechanics/weapontype.hpp +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -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, diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 4a832c60c..f1df6c90f 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -252,11 +252,15 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; - SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); - - mWeapon->getNode()->accept(findVisitor); - - return findVisitor.mFoundNode; + // 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); + bone = findVisitor.mFoundNode; + } + return bone; } osg::Node *CreatureWeaponAnimation::getWeaponNode() diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 31cf3f015..30942d75f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1078,10 +1078,15 @@ osg::Group* NpcAnimation::getArrowBone() int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; - SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); - part->getNode()->accept(findVisitor); - - return findVisitor.mFoundNode; + // 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); + bone = findVisitor.mFoundNode; + } + return bone; } osg::Node* NpcAnimation::getWeaponNode() diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index bf2b2b74f..9e8db49fd 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -242,33 +242,39 @@ Every weapon type has an attack animation group and a suffix for the movement an For example, long blades use ``weapononehand`` attack animation group, ``idle1h`` idle animation group, ``jump1h`` jumping animation group, etc. This is the full table of supported animation groups: -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Weapon type | Animation group | Movement suffix | Attack (fallback) | Suffix (fallback) | -+===============+===================+==================+======================+=======================+ -| Short blade | shortbladeonehand | 1s | weapononehand | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Long blade 1H | weapononehand | 1h | | | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Long blade 2H | weapontwohand | 2c | | | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Blunt 1H | bluntonehand | 1b | weapononehand | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Blunt 2H | blunttwohand | 2b | weapontwohand | 2c | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Axe 1H | bluntonehand | 1b | weapononehand | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Axe 2H | blunttwohand | 2b | weapontwohand | 2c | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Blunt 2H wide | weapontwowide | 2w | weapontwohand | 2c | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Spear | weapontwowide | 2w | weapontwohand | 2c | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Bow | bowandarrow | bow | | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Crossbow | crossbow | crossbow | | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ -| Thrown | throwweapon | 1t | | 1h | -+---------------+-------------------+------------------+----------------------+-----------------------+ ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Weapon type | Animation group | Movement suffix | Attack (fallback) | Suffix (fallback) | Attach bone | ++===============+===================+==================+======================+=======================+=======================+ +| Short blade | shortbladeonehand | 1s | weapononehand | 1h | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Long blade 1H | weapononehand | 1h | | | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Long blade 2H | weapontwohand | 2c | | | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Blunt 1H | bluntonehand | 1b | weapononehand | 1h | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Blunt 2H | blunttwohand | 2b | weapontwohand | 2c | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Axe 1H | bluntonehand | 1b | weapononehand | 1h | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Axe 2H | blunttwohand | 2b | weapontwohand | 2c | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Blunt 2H wide | weapontwowide | 2w | weapontwohand | 2c | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Spear | weapontwowide | 2w | weapontwohand | 2c | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Bow | bowandarrow | bow | | 1h | Weapon Bone Left | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Crossbow | crossbow | crossbow | | 1h | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ +| Thrown | throwweapon | 1t | | 1h | Weapon Bone | ++---------------+-------------------+------------------+----------------------+-----------------------+-----------------------+ + +Note that bows can be attached to the "Weapon Bone Left" bone if it is present in shooter's skeleton, and if it is not, "Weapon Bone" is used as a fallback. + +Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case OpenMW attaches arrows to this bone instead of ArrowBone in the bow mesh. +Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and +default arrow fetching animation does not look good). .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 From 82da2045a99417c6b3cf0784cdcc467002f91a12 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 14 Oct 2020 11:32:12 +0200 Subject: [PATCH 60/66] Non functionnal changes in preparation for async physics feature --- apps/openmw/mwphysics/actor.cpp | 18 ++-- apps/openmw/mwphysics/actor.hpp | 9 +- apps/openmw/mwphysics/object.cpp | 9 +- apps/openmw/mwphysics/object.hpp | 6 +- apps/openmw/mwphysics/physicssystem.cpp | 121 +++++++++++------------- apps/openmw/mwphysics/physicssystem.hpp | 27 +++--- apps/openmw/mwworld/worldimp.cpp | 22 ++--- 7 files changed, 101 insertions(+), 111 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0f8814aca..0688b9869 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -17,32 +17,29 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) +Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, btCollisionWorld* world) : 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) { 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; @@ -83,7 +80,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr Actor::~Actor() { - if (mCollisionObject.get()) + if (mCollisionObject) mCollisionWorld->removeCollisionObject(mCollisionObject.get()); } @@ -112,7 +109,7 @@ void Actor::updateCollisionMask() addCollisionMask(getCollisionMask()); } -int Actor::getCollisionMask() +int Actor::getCollisionMask() const { int collisionMask = CollisionType_World | CollisionType_HeightMap; if (mExternalCollisionMode) @@ -120,7 +117,6 @@ int Actor::getCollisionMask() if (mCanWaterWalk) collisionMask |= CollisionType_Water; return collisionMask; - } void Actor::updatePosition() diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 8752f7fee..a6ff838ad 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -21,12 +21,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 shape, btCollisionWorld* world); - ~Actor(); + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, btCollisionWorld* world); + ~Actor() override; /** * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. @@ -136,7 +137,7 @@ 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; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index f95a67823..b21a3e3ca 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,9 +14,10 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, btCollisionWorld* world) : mShapeInstance(shapeInstance) , mSolid(true) + , mCollisionWorld(world) { mPtr = ptr; @@ -31,6 +32,12 @@ namespace MWPhysics setOrigin(btVector3(pos[0], pos[1], pos[2])); } + Object::~Object() + { + if (mCollisionObject) + mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + } + const Resource::BulletShapeInstance* Object::getShapeInstance() const { return mShapeInstance.get(); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index b948433e3..87d1b0433 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -20,10 +20,11 @@ class btVector3; namespace MWPhysics { - class Object : public PtrHolder + class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, btCollisionWorld* world); + ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); @@ -42,6 +43,7 @@ namespace MWPhysics osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; + btCollisionWorld* mCollisionWorld; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 07859c1e2..58d96de28 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -65,11 +65,11 @@ namespace MWPhysics { mResourceSystem->addResourceManager(mShapeManager.get()); - mCollisionConfiguration = new btDefaultCollisionConfiguration(); - mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); - mBroadphase = new btDbvtBroadphase(); + mCollisionConfiguration = std::make_unique(); + mDispatcher = std::make_unique(mCollisionConfiguration.get()); + mBroadphase = std::make_unique(); - mCollisionWorld = new btCollisionWorld(mDispatcher, mBroadphase, mCollisionConfiguration); + mCollisionWorld = std::make_shared(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. @@ -92,7 +92,7 @@ namespace MWPhysics { mResourceSystem->removeResourceManager(mShapeManager.get()); - if (mWaterCollisionObject.get()) + if (mWaterCollisionObject) mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); for (auto& heightField : mHeightFields) @@ -101,21 +101,9 @@ namespace MWPhysics 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 +120,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; } @@ -175,7 +163,7 @@ namespace MWPhysics std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orient, - float queryDistance, std::vector targets) + float queryDistance, std::vector& targets) { // First of all, try to hit where you aim to int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; @@ -373,7 +361,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); } @@ -444,7 +432,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) @@ -481,11 +469,11 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - Object *obj = new Object(ptr, shapeInstance); + auto obj = std::make_shared(ptr, shapeInstance, mCollisionWorld.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) - mAnimatedObjects.insert(obj); + mAnimatedObjects.insert(obj.get()); mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); @@ -496,21 +484,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); } } @@ -536,19 +520,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); @@ -558,7 +542,7 @@ namespace MWPhysics { ActorMap::iterator found = mActors.find(ptr); if (found != mActors.end()) - return found->second; + return found->second.get(); return nullptr; } @@ -566,7 +550,7 @@ namespace MWPhysics { ActorMap::const_iterator found = mActors.find(ptr); if (found != mActors.end()) - return found->second; + return found->second.get(); return nullptr; } @@ -574,7 +558,7 @@ namespace MWPhysics { ObjectMap::const_iterator found = mObjects.find(ptr); if (found != mObjects.end()) - return found->second; + return found->second.get(); return nullptr; } @@ -639,11 +623,9 @@ namespace MWPhysics void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { osg::ref_ptr 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) @@ -652,8 +634,11 @@ namespace MWPhysics } } - Actor* actor = new Actor(ptr, shape, mCollisionWorld); - mActors.emplace(ptr, actor); + if (!shape) + return; + + auto actor = std::make_shared(ptr, shape, mCollisionWorld.get()); + mActors.emplace(ptr, std::move(actor)); } bool PhysicsSystem::toggleCollisionMode() @@ -691,14 +676,14 @@ namespace MWPhysics mStandingCollisions.clear(); } - const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) + const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt) { mMovementResults.clear(); mTimeAccum += dt; const int maxAllowedSteps = 20; - int numSteps = mTimeAccum / (mPhysicsDt); + int numSteps = mTimeAccum / mPhysicsDt; numSteps = std::min(numSteps, maxAllowedSteps); mTimeAccum -= numSteps * mPhysicsDt; @@ -711,26 +696,28 @@ namespace MWPhysics const MWWorld::Ptr player = MWMechanics::getPlayer(); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for(auto& movementItem : mMovementQueue) + for (const auto& m : mMovementQueue) { - ActorMap::iterator foundActor = mActors.find(movementItem.first); + const auto& character = m.first; + const auto& movement = m.second; + const auto foundActor = mActors.find(character); if (foundActor == mActors.end()) // actor was already removed from the scene continue; - Actor* physicActor = foundActor->second; + auto physicActor = foundActor->second; float waterlevel = -std::numeric_limits::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; 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)); @@ -742,8 +729,8 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - bool flying = world->isFlying(movementItem.first); - bool swimming = world->isSwimming(movementItem.first); + bool flying = world->isFlying(character); + bool swimming = world->isSwimming(character); bool wasOnGround = physicActor->getOnGround(); osg::Vec3f position = physicActor->getPosition(); @@ -751,8 +738,8 @@ namespace MWPhysics bool positionChanged = false; for (int i=0; igetPtr(), physicActor, movementItem.second, mPhysicsDt, - flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + position = MovementSolver::move(position, physicActor->getPtr(), physicActor.get(), movement, mPhysicsDt, + flying, waterlevel, slowFall, mCollisionWorld.get(), mStandingCollisions); if (position != physicActor->getPosition()) positionChanged = true; physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct @@ -765,25 +752,23 @@ namespace MWPhysics float heightDiff = position.z() - oldHeight; - MWMechanics::CreatureStats& stats = movementItem.first.getClass().getCreatureStats(movementItem.first); + MWMechanics::CreatureStats& stats = character.getClass().getCreatureStats(character); bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround()); if (isStillOnGround || flying || swimming || slowFall < 1) - stats.land(movementItem.first == player && (flying || swimming)); + stats.land(character == player && (flying || swimming)); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); - mMovementResults.emplace_back(movementItem.first, interpolated); + mMovementResults.emplace(character, interpolated); } - mMovementQueue.clear(); - return mMovementResults; } - void PhysicsSystem::stepSimulation(float dt) + void PhysicsSystem::stepSimulation() { - for (Object* animatedObject : mAnimatedObjects) - animatedObject->animateCollisionShapes(mCollisionWorld); + for (Object* animatedObject : mAnimatedObjects) + animatedObject->animateCollisionShapes(mCollisionWorld.get()); #ifndef BT_NO_PROFILE CProfileManager::Reset(); @@ -795,12 +780,12 @@ namespace MWPhysics { ObjectMap::iterator found = mObjects.find(object); if (found != mObjects.end()) - found->second->animateCollisionShapes(mCollisionWorld); + found->second->animateCollisionShapes(mCollisionWorld.get()); } void PhysicsSystem::debugDraw() { - if (mDebugDrawer.get()) + if (mDebugDrawer) mDebugDrawer->step(); } @@ -865,7 +850,7 @@ namespace MWPhysics void PhysicsSystem::updateWater() { - if (mWaterCollisionObject.get()) + if (mWaterCollisionObject) { mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 26005b396..fd9b52cb6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -47,7 +47,8 @@ class btCollisionShape; namespace MWPhysics { - typedef std::vector > PtrVelocityList; + using PtrPositionList = std::map; + using CollisionMap = std::map; class HeightField; class Object; @@ -93,7 +94,7 @@ namespace MWPhysics bool toggleCollisionMode(); - void stepSimulation(float dt); + void stepSimulation(); void debugDraw(); std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with @@ -102,7 +103,7 @@ namespace MWPhysics std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orientation, - float queryDistance, std::vector targets = std::vector()); + float queryDistance, std::vector& targets); /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the @@ -146,7 +147,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); /// Clear the queued movements list without applying. void clearQueuedMovement(); @@ -192,37 +193,37 @@ namespace MWPhysics osg::ref_ptr mUnrefQueue; - btBroadphaseInterface* mBroadphase; - btDefaultCollisionConfiguration* mCollisionConfiguration; - btCollisionDispatcher* mDispatcher; - btCollisionWorld* mCollisionWorld; + std::unique_ptr mBroadphase; + std::unique_ptr mCollisionConfiguration; + std::unique_ptr mDispatcher; + std::shared_ptr mCollisionWorld; std::unique_ptr mShapeManager; Resource::ResourceSystem* mResourceSystem; - typedef std::map ObjectMap; + using ObjectMap = std::map>; ObjectMap mObjects; std::set mAnimatedObjects; // stores pointers to elements in mObjects - typedef std::map ActorMap; + using ActorMap = std::map>; ActorMap mActors; - typedef std::map, HeightField*> HeightFieldMap; + using HeightFieldMap = std::map, HeightField *>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; // Tracks standing collisions happening during a single frame. // This will detect standing on an object, but won't detect running e.g. against a wall. - typedef std::map 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>; PtrVelocityList mMovementQueue; - PtrVelocityList mMovementResults; + PtrPositionList mMovementResults; float mTimeAccum; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 127fbb45a..e363fdd3b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1494,24 +1494,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) + const auto results = mPhysics->applyQueuedMovement(duration); + + for(const auto& result : results) { - if(iter->first == getPlayerPtr()) - { - // 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); + // Handle player last, in case a cell transition occurs + if(result.first != getPlayerPtr()) + moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false); } - if(player != results.end()) + + const auto player = results.find(getPlayerPtr()); + if (player != results.end()) moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); } From 4ef36973fb1e32201079d46ba68ba031d356c497 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 14 Oct 2020 15:47:50 +0200 Subject: [PATCH 61/66] Make the Actor class manage its collision object and position. --- apps/openmw/mwphysics/actor.cpp | 51 ++++++++++++++++++++++++--------- apps/openmw/mwphysics/actor.hpp | 5 ++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0688b9869..e8338cc17 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -76,6 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, btColl updatePosition(); addCollisionMask(getCollisionMask()); + commitPositionChange(); } Actor::~Actor() @@ -105,8 +106,7 @@ void Actor::addCollisionMask(int collisionMask) void Actor::updateCollisionMask() { - mCollisionWorld->removeCollisionObject(mCollisionObject.get()); - addCollisionMask(getCollisionMask()); + mCollisionObject->getBroadphaseHandle()->m_collisionFilterMask = getCollisionMask(); } int Actor::getCollisionMask() const @@ -126,29 +126,53 @@ void Actor::updatePosition() 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() +{ + 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()); + return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } void Actor::setPosition(const osg::Vec3f &position) { - mPreviousPosition = mPosition; + if (mTransformUpdatePending) + { + mCollisionObject->setWorldTransform(mLocalTransform); + mTransformUpdatePending = false; + } + else + { + mPreviousPosition = mPosition; - mPosition = position; - updateCollisionObjectPosition(); + mPosition = position; + updateCollisionObjectPosition(); + mCollisionObject->setWorldTransform(mLocalTransform); + } } osg::Vec3f Actor::getPosition() const @@ -163,11 +187,11 @@ osg::Vec3f Actor::getPreviousPosition() const void Actor::updateRotation () { - btTransform tr = mCollisionObject->getWorldTransform(); + if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude()) + return; mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); - tr.setRotation(Misc::Convert::toBullet(mRotation)); - mCollisionObject->setWorldTransform(tr); + mTransformUpdatePending = true; updateCollisionObjectPosition(); } @@ -183,12 +207,13 @@ void Actor::updateScale() 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(); } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index a6ff838ad..5393f9bfb 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -5,6 +5,7 @@ #include "ptrholder.hpp" +#include #include #include #include @@ -61,6 +62,7 @@ namespace MWPhysics void updatePosition(); void updateCollisionObjectPosition(); + void commitPositionChange(); /** * Returns the half extents of the collision body (scaled according to collision scale) @@ -157,6 +159,9 @@ namespace MWPhysics osg::Vec3f mRenderingScale; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; + btTransform mLocalTransform; + bool mScaleUpdatePending; + bool mTransformUpdatePending; osg::Vec3f mForce; bool mOnGround; From d76cc5d0a96a4348918f07ec1dcaf5858cace4dd Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 14 Oct 2020 15:55:15 +0200 Subject: [PATCH 62/66] Make the Object class manage its collision object and position. --- apps/openmw/mwphysics/object.cpp | 38 +++++++++++++++++++------ apps/openmw/mwphysics/object.hpp | 11 ++++++- apps/openmw/mwphysics/physicssystem.cpp | 10 +++++-- apps/openmw/mwworld/scene.cpp | 4 +-- apps/openmw/mwworld/worldimp.cpp | 4 +-- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index b21a3e3ca..c1eadc05d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -30,6 +30,7 @@ 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() @@ -45,17 +46,34 @@ namespace MWPhysics void Object::setScale(float scale) { - mShapeInstance->setLocalScaling(btVector3(scale, scale, scale)); + mScale = { scale,scale,scale }; + mScaleUpdatePending = true; } void Object::setRotation(const btQuaternion& quat) { - mCollisionObject->getWorldTransform().setRotation(quat); + mLocalTransform.setRotation(quat); + mTransformUpdatePending = true; } void Object::setOrigin(const btVector3& vec) { - mCollisionObject->getWorldTransform().setOrigin(vec); + mLocalTransform.setOrigin(vec); + mTransformUpdatePending = true; + } + + void Object::commitPositionChange() + { + if (mScaleUpdatePending) + { + mShapeInstance->setLocalScaling(mScale); + mScaleUpdatePending = false; + } + if (mTransformUpdatePending) + { + mCollisionObject->setWorldTransform(mLocalTransform); + mTransformUpdatePending = false; + } } btCollisionObject* Object::getCollisionObject() @@ -68,6 +86,11 @@ namespace MWPhysics return mCollisionObject.get(); } + btTransform Object::getTransform() const + { + return mLocalTransform; + } + bool Object::isSolid() const { return mSolid; @@ -83,10 +106,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()); @@ -107,7 +130,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()); @@ -129,7 +152,6 @@ namespace MWPhysics if (!(transform == compound->getChildTransform(shapeIndex))) compound->updateChildTransform(shapeIndex, transform); } - - collisionWorld->updateSingleAabb(mCollisionObject.get()); + return true; } } diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 87d1b0433..d1f3dff74 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -3,6 +3,7 @@ #include "ptrholder.hpp" +#include #include #include @@ -30,13 +31,17 @@ namespace MWPhysics 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 mCollisionObject; @@ -44,6 +49,10 @@ namespace MWPhysics std::map mRecIndexToNodePath; bool mSolid; btCollisionWorld* mCollisionWorld; + btVector3 mScale; + btTransform mLocalTransform; + bool mScaleUpdatePending; + bool mTransformUpdatePending; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 58d96de28..97be7c39e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -768,7 +768,12 @@ namespace MWPhysics void PhysicsSystem::stepSimulation() { for (Object* animatedObject : mAnimatedObjects) - animatedObject->animateCollisionShapes(mCollisionWorld.get()); + if (animatedObject->animateCollisionShapes()) + { + auto obj = mObjects.find(animatedObject->getPtr()); + assert(obj != mObjects.end()); + mCollisionWorld->updateSingleAabb(obj->second->getCollisionObject()); + } #ifndef BT_NO_PROFILE CProfileManager::Reset(); @@ -780,7 +785,8 @@ namespace MWPhysics { ObjectMap::iterator found = mObjects.find(object); if (found != mObjects.end()) - found->second->animateCollisionShapes(mCollisionWorld.get()); + if (found->second->animateCollisionShapes()) + mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); } void PhysicsSystem::debugDraw() diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 64316789f..684376226 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -152,7 +152,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() @@ -187,7 +187,7 @@ namespace *object->getShapeInstance()->getCollisionShape(), object->getShapeInstance()->getAvoidCollisionShape() }, - object->getCollisionObject()->getWorldTransform() + object->getTransform() ); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e363fdd3b..273a5f302 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1537,7 +1537,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 @@ -3880,7 +3880,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)); From 91b3926a49fbc3285c4b354764060f9243a1e419 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 15 Oct 2020 06:11:00 +0200 Subject: [PATCH 63/66] We need to update the collision world after each step. Change order of traversal simulation step to make it rare enough to be parallelizable Before: for actor in actors: repeat numstep: solve(actor) After: repeat numstep: for actor in actors: solve(actor) Introduce struct ActorFrameData to pack all data that is necessary for the solver --- apps/openmw/mwphysics/movementsolver.cpp | 90 ++++------ apps/openmw/mwphysics/movementsolver.hpp | 13 +- apps/openmw/mwphysics/physicssystem.cpp | 203 ++++++++++++++++++----- apps/openmw/mwphysics/physicssystem.hpp | 50 ++++++ 4 files changed, 255 insertions(+), 101 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 6345a76d9..3c78104dc 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -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& 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().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 &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - 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 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().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(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; } } diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 54a417fa7..75fba1cf0 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -5,13 +5,18 @@ #include -#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& standingCollisionTracker); + static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 97be7c39e..2fb556522 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -31,9 +31,11 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" @@ -687,14 +689,106 @@ namespace MWPhysics numSteps = std::min(numSteps, maxAllowedSteps); mTimeAccum -= numSteps * mPhysicsDt; + mActorsFrameData = prepareFrameData(); + bool advanceSimulation = (numSteps != 0); + if (advanceSimulation) + mWorldFrameData = std::make_unique(); - if (numSteps) + // update each actor position based on latest data + for (auto& data : mActorsFrameData) + data.updatePosition(); + + mMovementResults.clear(); + while (numSteps--) { - // Collision events should be available on every frame - mStandingCollisions.clear(); + for (auto& actorData : mActorsFrameData) + MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + + // update actors position + for (auto& actorData : mActorsFrameData) + { + if(const auto actor = actorData.mActor.lock()) + { + if (actorData.mPosition != actorData.mActorRaw->getPosition()) + actorData.mPositionChanged = true; + actorData.mActorRaw->setPosition(actorData.mPosition); + } + } } - const MWWorld::Ptr player = MWMechanics::getPlayer(); + for (auto& actorData : mActorsFrameData) + { + // handle fall of actor + const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; + + const bool isStillOnGround = (advanceSimulation && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); + + if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) + actorData.mNeedLand = true; + else if (heightDiff < 0) + actorData.mFallHeight += heightDiff; + + // interpolate position + const float interpolationFactor = mTimeAccum / mPhysicsDt; + mMovementResults[actorData.mPtr] = actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); + + // update mechanics if actor jumped + if (actorData.mDidJump) + { + const bool isPlayer = (actorData.mPtr == MWMechanics::getPlayer()); + // Advance acrobatics and set flag for GetPCJumping + if (isPlayer) + { + actorData.mPtr.getClass().skillUsageSucceeded(actorData.mPtr, ESM::Skill::Acrobatics, 0); + MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + } + + // Decrease fatigue + if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) + { + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); + const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); + const float normalizedEncumbrance = std::min(1.f, actorData.mPtr.getClass().getNormalizedEncumbrance(actorData.mPtr)); + const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; + MWMechanics::DynamicStat fatigue = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).setFatigue(fatigue); + } + actorData.mPtr.getClass().getMovementSettings(actorData.mPtr).mPosition[2] = 0; + } + + 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); + } + + // update actors aabb + for (const auto& actorData : mActorsFrameData) + if (actorData.mPositionChanged) + mCollisionWorld->updateSingleAabb(actorData.mActorRaw->getCollisionObject()); + + // update standing collisions map + if (advanceSimulation) + { + mStandingCollisions.clear(); + for (auto& actorData : mActorsFrameData) + { + if (!actorData.mStandingOn.isEmpty()) + mStandingCollisions[actorData.mPtr] = actorData.mStandingOn; + else + mStandingCollisions.erase(actorData.mPtr); + } + } + return mMovementResults; + } + + std::vector PhysicsSystem::prepareFrameData() + { + std::vector actorsFrameData; + actorsFrameData.reserve(mMovementQueue.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& m : mMovementQueue) { @@ -702,7 +796,10 @@ namespace MWPhysics 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; + } auto physicActor = foundActor->second; float waterlevel = -std::numeric_limits::max(); @@ -713,56 +810,27 @@ namespace MWPhysics 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(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) waterCollision = true; 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(character); - bool swimming = world->isSwimming(character); - - bool wasOnGround = physicActor->getOnGround(); - osg::Vec3f position = physicActor->getPosition(); - float oldHeight = position.z(); - bool positionChanged = false; - for (int i=0; igetPtr(), physicActor.get(), movement, mPhysicsDt, - flying, waterlevel, slowFall, mCollisionWorld.get(), mStandingCollisions); - if (position != physicActor->getPosition()) - positionChanged = true; - physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct - } - if (positionChanged) - mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject()); - - float interpolationFactor = mTimeAccum / mPhysicsDt; - osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); - - float heightDiff = position.z() - oldHeight; - - MWMechanics::CreatureStats& stats = character.getClass().getCreatureStats(character); - bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround()); - if (isStillOnGround || flying || swimming || slowFall < 1) - stats.land(character == player && (flying || swimming)); - else if (heightDiff < 0) - stats.addToFallHeight(-heightDiff); - - mMovementResults.emplace(character, interpolated); + actorsFrameData.emplace_back(std::move(physicActor), character, mStandingCollisions[character], moveToWaterSurface, movement, slowFall, waterlevel); } mMovementQueue.clear(); - return mMovementResults; + return actorsFrameData; } void PhysicsSystem::stepSimulation() @@ -896,4 +964,61 @@ namespace MWPhysics stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); } + + ActorFrameData::ActorFrameData(const std::shared_ptr& 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& a1, const std::weak_ptr& 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; + } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index fd9b52cb6..b9390e9de 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H +#include #include #include #include @@ -53,6 +54,51 @@ namespace MWPhysics class HeightField; class Object; class Actor; + class PhysicsTaskScheduler; + + struct LOSRequest + { + LOSRequest(const std::weak_ptr& a1, const std::weak_ptr& a2); + std::array, 2> mActors; + std::array mRawActors; + bool mResult; + bool mStale; + int mAge; + }; + bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept; + + struct ActorFrameData + { + ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); + void updatePosition(); + std::weak_ptr 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 { @@ -191,6 +237,8 @@ namespace MWPhysics void updateWater(); + std::vector prepareFrameData(); + osg::ref_ptr mUnrefQueue; std::unique_ptr mBroadphase; @@ -224,6 +272,8 @@ namespace MWPhysics using PtrVelocityList = std::vector>; PtrVelocityList mMovementQueue; PtrPositionList mMovementResults; + std::unique_ptr mWorldFrameData; + std::vector mActorsFrameData; float mTimeAccum; From 3c2504b4420ede190de69efa465c67d50b98759c Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 15 Oct 2020 06:11:44 +0200 Subject: [PATCH 64/66] Process movement queue in one or several background threads Before movement calculation, the main thread prepare a vector of ActorFrameData, which contains all data necessary to perform the simulation, and feed it to the solver. At the same time it fetches the result from the previous background simulation, which in turn is used by the game mechanics. Other functions of the physics system (weapon hit for instance) interrupt the background simulation, with some exceptions described below. The number of threads is controlled by the numeric setting [Physics] async num threads In case 'async num threads' > 1 and Bullet doesn't support multiple threads, 1 async thread will be used. 0 means synchronous solver. Additional settings (will be silently switched off if async num threads = 0) [Physics] defer aabb update Update AABBs of actors and objects in the background thread(s). It is not an especially costly operation, but it needs exclusive access to the collision world, which blocks other operations. Since AABB needs to be updated for collision detection, one can queue them to defer update before start of the movement solver. Extensive tests on as much as one installation (mine) show no drawback having that switched on. [Physics] lineofsight keep inactive cache Control for how long (how many frames) the line of sight (LOS) request will be kept updated. When a request for LOS is made for the first time, the background threads are stopped to service it. From now on, the LOS will be refreshed preemptively as part of the background routine until it is not required for lineofsight keep inactive cache frames. This mean that subsequent request will not interrupt the background computation. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actor.cpp | 42 +- apps/openmw/mwphysics/actor.hpp | 25 +- apps/openmw/mwphysics/mtphysics.cpp | 607 ++++++++++++++++++++++++ apps/openmw/mwphysics/mtphysics.hpp | 94 ++++ apps/openmw/mwphysics/object.cpp | 13 +- apps/openmw/mwphysics/object.hpp | 9 +- apps/openmw/mwphysics/physicssystem.cpp | 186 ++------ apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 7 +- apps/openmw/mwworld/worldimp.hpp | 1 + components/misc/barrier.hpp | 51 ++ files/settings-default.cfg | 12 + 13 files changed, 872 insertions(+), 183 deletions(-) create mode 100644 apps/openmw/mwphysics/mtphysics.cpp create mode 100644 apps/openmw/mwphysics/mtphysics.hpp create mode 100644 components/misc/barrier.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b718322ac..d943c7836 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e8338cc17..5caaba5c9 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -12,18 +12,19 @@ #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "mtphysics.hpp" namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& 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), 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; @@ -82,12 +83,12 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, btColl Actor::~Actor() { if (mCollisionObject) - mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) { - mInternalCollisionMode = collision; + mInternalCollisionMode.store(collision, std::memory_order_release); } void Actor::enableCollisionBody(bool collision) @@ -101,12 +102,12 @@ 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() { - mCollisionObject->getBroadphaseHandle()->m_collisionFilterMask = getCollisionMask(); + mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask()); } int Actor::getCollisionMask() const @@ -121,6 +122,7 @@ int Actor::getCollisionMask() const void Actor::updatePosition() { + std::unique_lock lock(mPositionMutex); osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); mPosition = position; @@ -141,6 +143,7 @@ void Actor::updateCollisionObjectPosition() void Actor::commitPositionChange() { + std::unique_lock lock(mPositionMutex); if (mScaleUpdatePending) { mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); @@ -155,11 +158,13 @@ void Actor::commitPositionChange() osg::Vec3f Actor::getCollisionObjectPosition() const { + std::unique_lock 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 lock(mPositionMutex); if (mTransformUpdatePending) { mCollisionObject->setWorldTransform(mLocalTransform); @@ -170,23 +175,29 @@ void Actor::setPosition(const osg::Vec3f &position) mPreviousPosition = mPosition; mPosition = position; - updateCollisionObjectPosition(); - mCollisionObject->setWorldTransform(mLocalTransform); + if (updateCollisionObject) + { + updateCollisionObjectPosition(); + mCollisionObject->setWorldTransform(mLocalTransform); + } } } osg::Vec3f Actor::getPosition() const { + std::unique_lock lock(mPositionMutex); return mPosition; } osg::Vec3f Actor::getPreviousPosition() const { + std::unique_lock lock(mPositionMutex); return mPreviousPosition; } void Actor::updateRotation () { + std::unique_lock lock(mPositionMutex); if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude()) return; mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); @@ -202,6 +213,7 @@ bool Actor::isRotationallyInvariant() const void Actor::updateScale() { + std::unique_lock lock(mPositionMutex); float scale = mPtr.getCellRef().getScale(); osg::Vec3f scaleVec(scale,scale,scale); @@ -219,16 +231,19 @@ void Actor::updateScale() osg::Vec3f Actor::getHalfExtents() const { + std::unique_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mScale); } osg::Vec3f Actor::getOriginalHalfExtents() const { + std::unique_lock lock(mPositionMutex); return mHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { + std::unique_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mRenderingScale); } @@ -239,26 +254,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 lock(mPositionMutex); if (waterWalk != mCanWaterWalk) { mCanWaterWalk = waterWalk; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 5393f9bfb..ef7b368b9 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -1,7 +1,9 @@ #ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H +#include #include +#include #include "ptrholder.hpp" @@ -10,7 +12,6 @@ #include #include -class btCollisionWorld; class btCollisionShape; class btCollisionObject; class btConvexShape; @@ -27,7 +28,7 @@ namespace MWPhysics class Actor final : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, btCollisionWorld* world); + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler); ~Actor() override; /** @@ -37,7 +38,7 @@ namespace MWPhysics bool getCollisionMode() const { - return mInternalCollisionMode; + return mInternalCollisionMode.load(std::memory_order_acquire); } btConvexShape* getConvexShape() const { return mConvexShape; } @@ -82,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; @@ -113,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 @@ -142,7 +144,7 @@ namespace MWPhysics int getCollisionMask() const; bool mCanWaterWalk; - bool mWalkingOnWater; + std::atomic mWalkingOnWater; bool mRotationallyInvariant; @@ -162,14 +164,15 @@ namespace MWPhysics btTransform mLocalTransform; bool mScaleUpdatePending; bool mTransformUpdatePending; + mutable std::mutex mPositionMutex; osg::Vec3f mForce; - bool mOnGround; - bool mOnSlope; - bool mInternalCollisionMode; + std::atomic mOnGround; + std::atomic mOnSlope; + std::atomic mInternalCollisionMode; bool mExternalCollisionMode; - btCollisionWorld* mCollisionWorld; + PhysicsTaskScheduler* mTaskScheduler; Actor(const Actor&); Actor& operator=(const Actor&); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp new file mode 100644 index 000000000..9aff85359 --- /dev/null +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -0,0 +1,607 @@ +#include +#include + +#include "components/debug/debuglog.hpp" +#include +#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 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 &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + 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 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 + using void_t = void; + + /// @brief for Bullet <= 2.87 + template + 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 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 {}; + + btScalar parallelSum(int iBegin, int iEnd, int grainSize, const btIParallelSumBody& body) override { return {}; }; + }; + + using MultiThreadedBullet = MultiThreadedBulletImpl; + + 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(); + 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 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(mNumThreads, [&]() + { + updateAabbs(); + }); + + mPostStepBarrier = std::make_unique(mNumThreads, [&]() + { + if (mRemainingSteps) + --mRemainingSteps; + mNextJob.store(0, std::memory_order_release); + updateActorsPositions(); + }); + + mPostSimBarrier = std::make_unique(mNumThreads, [&]() + { + udpateActorsAabbs(); + mNewFrame = false; + if (mLOSCacheExpiry >= 0) + { + std::unique_lock lock(mLOSCacheMutex); + mLOSCache.erase( + std::remove_if(mLOSCache.begin(), mLOSCache.end(), + [](const LOSRequest& req) { return req.mStale; }), + mLOSCache.end()); + } + }); + } + + PhysicsTaskScheduler::~PhysicsTaskScheduler() + { + std::unique_lock 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&& 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 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(); + + // 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 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 lock(mCollisionWorldMutex, mThreadSafeBullet); + mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); + } + + void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) + { + std::shared_lock lock(mCollisionWorldMutex); + mCollisionWorld->contactTest(colObj, resultCallback); + } + + boost::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) + { + MaybeSharedLock 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 lock(mCollisionWorldMutex); + mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); + } + + void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) + { + std::shared_lock lock(mCollisionWorldMutex); + obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); + } + + void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) + { + std::unique_lock lock(mCollisionWorldMutex); + collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; + } + + void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) + { + std::unique_lock lock(mCollisionWorldMutex); + mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); + } + + void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) + { + std::unique_lock lock(mCollisionWorldMutex); + mCollisionWorld->removeCollisionObject(collisionObject); + } + + void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr) + { + if (mDeferAabbUpdate) + { + std::unique_lock lock(mUpdateAabbMutex); + mUpdateAabb.insert(std::move(ptr)); + } + else + { + std::unique_lock lock(mCollisionWorldMutex); + updatePtrAabb(ptr); + } + } + + bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2) + { + std::unique_lock 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 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 lock1(mCollisionWorldMutex, std::defer_lock); + std::unique_lock lock2(mUpdateAabbMutex, std::defer_lock); + std::lock(lock1, lock2); + std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), + [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); + mUpdateAabb.clear(); + } + + void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr& ptr) + { + if (const auto p = ptr.lock()) + { + if (const auto actor = std::dynamic_pointer_cast(p)) + { + actor->commitPositionChange(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + else if (const auto object = std::dynamic_pointer_cast(p)) + { + object->commitPositionChange(); + mCollisionWorld->updateSingleAabb(object->getCollisionObject()); + } + }; + } + + void PhysicsTaskScheduler::worker() + { + std::shared_lock 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 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 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 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 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(); + } +} diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp new file mode 100644 index 000000000..4862393f3 --- /dev/null +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -0,0 +1,94 @@ +#ifndef OPENMW_MWPHYSICS_MTPHYSICS_H +#define OPENMW_MWPHYSICS_MTPHYSICS_H + +#include +#include +#include +#include + +#include +#include + +#include "physicssystem.hpp" +#include "ptrholder.hpp" + +namespace Misc +{ + class Barrier; +} + +namespace MWPhysics +{ + class PhysicsTaskScheduler + { + public: + PhysicsTaskScheduler(float physicsDt, std::shared_ptr 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&& 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 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 ptr); + bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& 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& ptr); + + std::unique_ptr mWorldFrameData; + std::vector mActorsFrameData; + PtrPositionList mMovementResults; + PtrPositionList mPreviousMovementResults; + const float mPhysicsDt; + float mTimeAccum; + std::shared_ptr mCollisionWorld; + std::vector mLOSCache; + std::set, std::owner_less>> mUpdateAabb; + + // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing + std::unique_ptr mPreStepBarrier; + std::unique_ptr mPostStepBarrier; + std::unique_ptr mPostSimBarrier; + + int mNumThreads; + int mNumJobs; + int mRemainingSteps; + int mLOSCacheExpiry; + bool mDeferAabbUpdate; + bool mNewFrame; + bool mAdvanceSimulation; + bool mThreadSafeBullet; + bool mQuit; + std::atomic mNextJob; + std::atomic mNextLOS; + std::vector 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 diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index c1eadc05d..c822bbcbe 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -1,4 +1,5 @@ #include "object.hpp" +#include "mtphysics.hpp" #include #include @@ -8,16 +9,15 @@ #include #include -#include #include namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, btCollisionWorld* world) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) - , mCollisionWorld(world) + , mTaskScheduler(scheduler) { mPtr = ptr; @@ -36,7 +36,7 @@ namespace MWPhysics Object::~Object() { if (mCollisionObject) - mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } const Resource::BulletShapeInstance* Object::getShapeInstance() const @@ -46,24 +46,28 @@ namespace MWPhysics void Object::setScale(float scale) { + std::unique_lock lock(mPositionMutex); mScale = { scale,scale,scale }; mScaleUpdatePending = true; } void Object::setRotation(const btQuaternion& quat) { + std::unique_lock lock(mPositionMutex); mLocalTransform.setRotation(quat); mTransformUpdatePending = true; } void Object::setOrigin(const btVector3& vec) { + std::unique_lock lock(mPositionMutex); mLocalTransform.setOrigin(vec); mTransformUpdatePending = true; } void Object::commitPositionChange() { + std::unique_lock lock(mPositionMutex); if (mScaleUpdatePending) { mShapeInstance->setLocalScaling(mScale); @@ -88,6 +92,7 @@ namespace MWPhysics btTransform Object::getTransform() const { + std::unique_lock lock(mPositionMutex); return mLocalTransform; } diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index d1f3dff74..876e35651 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace Resource { @@ -15,16 +16,17 @@ namespace Resource } class btCollisionObject; -class btCollisionWorld; class btQuaternion; class btVector3; namespace MWPhysics { + class PhysicsTaskScheduler; + class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, btCollisionWorld* world); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; @@ -48,11 +50,12 @@ namespace MWPhysics osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; - btCollisionWorld* mCollisionWorld; btVector3 mScale; btTransform mLocalTransform; bool mScaleUpdatePending; bool mTransformUpdatePending; + mutable std::mutex mPositionMutex; + PhysicsTaskScheduler* mTaskScheduler; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2fb556522..00068c1e6 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -35,7 +35,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" @@ -52,6 +51,7 @@ #include "contacttestresultcallback.hpp" #include "constants.hpp" #include "movementsolver.hpp" +#include "mtphysics.hpp" namespace MWPhysics { @@ -88,6 +88,8 @@ namespace MWPhysics Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS)."; } } + + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld); } PhysicsSystem::~PhysicsSystem() @@ -95,11 +97,11 @@ namespace MWPhysics mResourceSystem->removeResourceManager(mShapeManager.get()); if (mWaterCollisionObject) - mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); for (auto& heightField : mHeightFields) { - mCollisionWorld->removeCollisionObject(heightField.second->getCollisionObject()); + mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject()); delete heightField.second; } @@ -211,7 +213,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) { @@ -235,21 +237,12 @@ 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(); + // didn't hit the target. this could happen if point is already inside the collision box + return 0.f; } RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const @@ -293,7 +286,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(); @@ -319,7 +312,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(); @@ -333,18 +326,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 + { + 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(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); - - return !result.mHit; + return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2)); } bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) @@ -398,7 +388,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)); } @@ -424,7 +414,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; } @@ -442,7 +432,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); } @@ -451,7 +441,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); } @@ -471,13 +461,13 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, mCollisionWorld.get()); + auto obj = std::make_shared(ptr, shapeInstance, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); - mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType, + mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } @@ -571,14 +561,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; } } @@ -589,7 +579,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); @@ -598,7 +588,7 @@ namespace MWPhysics if (!foundActor->second->isRotationallyInvariant()) { foundActor->second->updateRotation(); - mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + mTaskScheduler->updateSingleAabb(foundActor->second); } return; } @@ -610,14 +600,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; } } @@ -639,7 +629,7 @@ namespace MWPhysics if (!shape) return; - auto actor = std::make_shared(ptr, shape, mCollisionWorld.get()); + auto actor = std::make_shared(ptr, shape, mTaskScheduler.get()); mActors.emplace(ptr, std::move(actor)); } @@ -678,10 +668,8 @@ namespace MWPhysics mStandingCollisions.clear(); } - const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt) + const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation) { - mMovementResults.clear(); - mTimeAccum += dt; const int maxAllowedSteps = 20; @@ -689,100 +677,8 @@ namespace MWPhysics numSteps = std::min(numSteps, maxAllowedSteps); mTimeAccum -= numSteps * mPhysicsDt; - mActorsFrameData = prepareFrameData(); - bool advanceSimulation = (numSteps != 0); - if (advanceSimulation) - mWorldFrameData = std::make_unique(); - // update each actor position based on latest data - for (auto& data : mActorsFrameData) - data.updatePosition(); - - mMovementResults.clear(); - while (numSteps--) - { - for (auto& actorData : mActorsFrameData) - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); - - // update actors position - for (auto& actorData : mActorsFrameData) - { - if(const auto actor = actorData.mActor.lock()) - { - if (actorData.mPosition != actorData.mActorRaw->getPosition()) - actorData.mPositionChanged = true; - actorData.mActorRaw->setPosition(actorData.mPosition); - } - } - } - - for (auto& actorData : mActorsFrameData) - { - // handle fall of actor - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - - const bool isStillOnGround = (advanceSimulation && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); - - if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) - actorData.mNeedLand = true; - else if (heightDiff < 0) - actorData.mFallHeight += heightDiff; - - // interpolate position - const float interpolationFactor = mTimeAccum / mPhysicsDt; - mMovementResults[actorData.mPtr] = actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); - - // update mechanics if actor jumped - if (actorData.mDidJump) - { - const bool isPlayer = (actorData.mPtr == MWMechanics::getPlayer()); - // Advance acrobatics and set flag for GetPCJumping - if (isPlayer) - { - actorData.mPtr.getClass().skillUsageSucceeded(actorData.mPtr, ESM::Skill::Acrobatics, 0); - MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); - } - - // Decrease fatigue - if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) - { - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); - const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); - const float normalizedEncumbrance = std::min(1.f, actorData.mPtr.getClass().getNormalizedEncumbrance(actorData.mPtr)); - const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; - MWMechanics::DynamicStat fatigue = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).getFatigue(); - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).setFatigue(fatigue); - } - actorData.mPtr.getClass().getMovementSettings(actorData.mPtr).mPosition[2] = 0; - } - - 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); - } - - // update actors aabb - for (const auto& actorData : mActorsFrameData) - if (actorData.mPositionChanged) - mCollisionWorld->updateSingleAabb(actorData.mActorRaw->getCollisionObject()); - - // update standing collisions map - if (advanceSimulation) - { - mStandingCollisions.clear(); - for (auto& actorData : mActorsFrameData) - { - if (!actorData.mStandingOn.isEmpty()) - mStandingCollisions[actorData.mPtr] = actorData.mStandingOn; - else - mStandingCollisions.erase(actorData.mPtr); - } - } - return mMovementResults; + return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(), mStandingCollisions, skipSimulation); } std::vector PhysicsSystem::prepareFrameData() @@ -840,7 +736,7 @@ namespace MWPhysics { auto obj = mObjects.find(animatedObject->getPtr()); assert(obj != mObjects.end()); - mCollisionWorld->updateSingleAabb(obj->second->getCollisionObject()); + mTaskScheduler->updateSingleAabb(obj->second); } #ifndef BT_NO_PROFILE @@ -854,7 +750,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(object); if (found != mObjects.end()) if (found->second->animateCollisionShapes()) - mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); + mTaskScheduler->updateSingleAabb(found->second); } void PhysicsSystem::debugDraw() @@ -926,7 +822,7 @@ namespace MWPhysics { if (mWaterCollisionObject) { - mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); } if (!mWaterEnabled) @@ -938,7 +834,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); } @@ -954,7 +850,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(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b9390e9de..e5ee925c4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -193,7 +193,7 @@ namespace MWPhysics void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Apply all queued movements, then clear the list. - const PtrPositionList& applyQueuedMovement(float dt); + const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation); /// Clear the queued movements list without applying. void clearQueuedMovement(); @@ -245,6 +245,7 @@ namespace MWPhysics std::unique_ptr mCollisionConfiguration; std::unique_ptr mDispatcher; std::shared_ptr mCollisionWorld; + std::unique_ptr mTaskScheduler; std::unique_ptr mShapeManager; Resource::ResourceSystem* mResourceSystem; @@ -271,9 +272,6 @@ namespace MWPhysics using PtrVelocityList = std::vector>; PtrVelocityList mMovementQueue; - PtrPositionList mMovementResults; - std::unique_ptr mWorldFrameData; - std::vector mActorsFrameData; float mTimeAccum; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 273a5f302..38bafa61b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -144,7 +144,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), @@ -932,6 +932,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) { @@ -951,6 +952,7 @@ namespace MWWorld void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); + mDiscardMovements = true; if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace) { @@ -1499,7 +1501,8 @@ namespace MWWorld mProjectileManager->update(duration); - const auto results = mPhysics->applyQueuedMovement(duration); + const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements); + mDiscardMovements = false; for(const auto& result : results) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9ba24541d..c10c7ea5a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -102,6 +102,7 @@ namespace MWWorld bool mSky; bool mGodMode; bool mScriptsEnabled; + bool mDiscardMovements; std::vector mContentFiles; std::string mUserDataPath; diff --git a/components/misc/barrier.hpp b/components/misc/barrier.hpp new file mode 100644 index 000000000..7259b8452 --- /dev/null +++ b/components/misc/barrier.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_BARRIER_H +#define OPENMW_BARRIER_H + +#include +#include +#include + +namespace Misc +{ + /// @brief Synchronize several threads + class Barrier + { + public: + using BarrierCallback = std::function; + /// @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(func)) + {} + + /// @brief stop execution of threads until count distinct threads reach this point + void wait() + { + std::unique_lock 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 diff --git a/files/settings-default.cfg b/files/settings-default.cfg index fab5fe569..3c6e736d8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -916,3 +916,15 @@ object shadows = false # Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting. enable indoor shadows = true + +[Physics] +# how much background thread to use in the physics solver. 0 to disable (i.e solver run in the main thread) +async num threads = 0 + +# maintain a cache of lineofsight request in the bacground physics thread +# determines for how much frames an inactive lineofsight request should be kept updated in the cache +# -1 to disable (i.e the LOS will be calculated only on request) +lineofsight keep inactive cache = 0 + +# wether to defer aabb update till before collision detection +defer aabb update = true From ae3306c01920377c906fb66ad18eb47f1f92687f Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 14 Oct 2020 22:26:21 +0200 Subject: [PATCH 65/66] Document async physics settings Add an option to the launcher Update changelog --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 6 +++ .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/physics.rst | 37 +++++++++++++++++++ files/ui/advancedpage.ui | 30 +++++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 docs/source/reference/modding/settings/physics.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 80253e99c..3d2f315aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ 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 Task #5480: Drop Qt4 support diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 2e929faf5..bd3f6979e 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -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 diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index a31ce0d53..2261fe8e1 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -57,3 +57,4 @@ The ranges included with each setting are the physically possible ranges, not re water windows navigator + physics diff --git a/docs/source/reference/modding/settings/physics.rst b/docs/source/reference/modding/settings/physics.rst new file mode 100644 index 000000000..59aba91aa --- /dev/null +++ b/docs/source/reference/modding/settings/physics.rst @@ -0,0 +1,37 @@ +Physics Settings +################ + +async num threads +----------------- + +:Type: integer +:Range: >= 0 +:Default: 0 + +Determines how many threads will be spawned to compute physics update in the background (that is, process actors movement). A value of 0 means that the update will be performed in the main thread. +A value greater than 1 requires the Bullet library be compiled with multithreading support. If that's not the case, a warning will be written in ``openmw.log`` and a value of 1 will be used. + +lineofsight keep inactive cache +------------------------------- + +:Type: integer +:Range: >= -1 +:Default: 0 + +The line of sight determines if 2 actors can see each other (without taking into account game mechanics such as invisibility or sneaking). It is used by some scripts (the getLOS function), by the AI (to determine if an actor should start combat or chase an opponent) and for functionnalities such as greetings or turning NPC head toward an object. +This parameters determine for how long a cache of request should be kept warm. It depends on :ref:`async num threads` being > 0, otherwise a value of -1 will be used. If a request is not found in the cache, it is always fulfilled immediately. In case Bullet is compiled without multithreading support, non-cached requests involve blocking the async thread(s), which might hurt performance. +A value of -1 means no caching. +A value of 0 means that for as long as a request is made (after the first one), it will be preemptively "refreshed" in the async thread, without blocking neither the main thread nor the async thread. +Any value > 0 is the number of frames for which the values are kept in cache even if the results was not requested again. +If Bullet is compiled with multithreading support, requests are non blocking, it is better to set this parameter to -1. + +defer aabb update +----------------- + +:Type: boolean +:Range: True/False +:Default: True + +Axis-aligned bounding box (aabb for short) are used by Bullet for collision detection. They should be updated anytime a physical object is modified (for instance moved) for collision detection to be correct. +This parameter control wether the update should be done as soon as the object is modified (the default), which involves blocking the async thread(s), or queue the modifications to update them as a batch before the collision detections. It depends on :ref:`async num threads` being > 0, otherwise it will be disabled. +Disabling this parameter is intended as an aid for debugging collisions detection issues. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 3a91db791..6cb73e7ef 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -211,6 +211,36 @@ + + + + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + + + Background physics threads + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + From b024518c180644d89ffff3db76f12bbe334dfef2 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 15 Oct 2020 11:12:23 +0200 Subject: [PATCH 66/66] Resolve 'shared_timed_mutex' is unavailable: introduced in macOS 10.12 --- CI/before_script.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 993de79fe..0eeef60a4 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -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 \