diff --git a/.gitignore b/.gitignore index 9734ac35c..776e2b659 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ makefile data *.kdev4 CMakeLists.txt.user +*.swp +*.swo diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8b1378917..000000000 --- a/.gitmodules +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Bitstream Vera License.txt b/Bitstream Vera License.txt deleted file mode 100644 index 2b37cc1df..000000000 --- a/Bitstream Vera License.txt +++ /dev/null @@ -1,123 +0,0 @@ -Bitstream Vera Fonts Copyright - -The fonts have a generous copyright, allowing derivative works (as -long as "Bitstream" or "Vera" are not in the names), and full -redistribution (so long as they are not *sold* by themselves). They -can be be bundled, redistributed and sold with any software. - -The fonts are distributed under the following copyright: - -Copyright -========= - -Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream -Vera is a trademark of Bitstream, Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the fonts accompanying this license ("Fonts") and associated -documentation files (the "Font Software"), to reproduce and distribute -the Font Software, including without limitation the rights to use, -copy, merge, publish, distribute, and/or sell copies of the Font -Software, and to permit persons to whom the Font Software is furnished -to do so, subject to the following conditions: - -The above copyright and trademark notices and this permission notice -shall be included in all copies of one or more of the Font Software -typefaces. - -The Font Software may be modified, altered, or added to, and in -particular the designs of glyphs or characters in the Fonts may be -modified and additional glyphs or characters may be added to the -Fonts, only if the fonts are renamed to names not containing either -the words "Bitstream" or the word "Vera". - -This License becomes null and void to the extent applicable to Fonts -or Font Software that has been modified and is distributed under the -"Bitstream Vera" names. - -The Font Software may be sold as part of a larger software package but -no copy of one or more of the Font Software typefaces may be sold by -itself. - -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL -BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, -OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT -SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. - -Except as contained in this notice, the names of Gnome, the Gnome -Foundation, and Bitstream Inc., shall not be used in advertising or -otherwise to promote the sale, use or other dealings in this Font -Software without prior written authorization from the Gnome Foundation -or Bitstream Inc., respectively. For further information, contact: -fonts at gnome dot org. - -Copyright FAQ -============= - - 1. I don't understand the resale restriction... What gives? - - Bitstream is giving away these fonts, but wishes to ensure its - competitors can't just drop the fonts as is into a font sale system - and sell them as is. It seems fair that if Bitstream can't make money - from the Bitstream Vera fonts, their competitors should not be able to - do so either. You can sell the fonts as part of any software package, - however. - - 2. I want to package these fonts separately for distribution and - sale as part of a larger software package or system. Can I do so? - - Yes. A RPM or Debian package is a "larger software package" to begin - with, and you aren't selling them independently by themselves. - See 1. above. - - 3. Are derivative works allowed? - Yes! - - 4. Can I change or add to the font(s)? - Yes, but you must change the name(s) of the font(s). - - 5. Under what terms are derivative works allowed? - - You must change the name(s) of the fonts. This is to ensure the - quality of the fonts, both to protect Bitstream and Gnome. We want to - ensure that if an application has opened a font specifically of these - names, it gets what it expects (though of course, using fontconfig, - substitutions could still could have occurred during font - opening). You must include the Bitstream copyright. Additional - copyrights can be added, as per copyright law. Happy Font Hacking! - - 6. If I have improvements for Bitstream Vera, is it possible they might get - adopted in future versions? - - Yes. The contract between the Gnome Foundation and Bitstream has - provisions for working with Bitstream to ensure quality additions to - the Bitstream Vera font family. Please contact us if you have such - additions. Note, that in general, we will want such additions for the - entire family, not just a single font, and that you'll have to keep - both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add - glyphs to the font, they must be stylistically in keeping with Vera's - design. Vera cannot become a "ransom note" font. Jim Lyles will be - providing a document describing the design elements used in Vera, as a - guide and aid for people interested in contributing to Vera. - - 7. I want to sell a software package that uses these fonts: Can I do so? - - Sure. Bundle the fonts with your software and sell your software - with the fonts. That is the intent of the copyright. - - 8. If applications have built the names "Bitstream Vera" into them, - can I override this somehow to use fonts of my choosing? - - This depends on exact details of the software. Most open source - systems and software (e.g., Gnome, KDE, etc.) are now converting to - use fontconfig (see www.fontconfig.org) to handle font configuration, - selection and substitution; it has provisions for overriding font - names and subsituting alternatives. An example is provided by the - supplied local.conf file, which chooses the family Bitstream Vera for - "sans", "serif" and "monospace". Other software (e.g., the XFree86 - core server) has other mechanisms for font substitution. diff --git a/CMakeLists.txt b/CMakeLists.txt index 68c3204fd..395027479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 20) +set (OPENMW_VERSION_MINOR 21) set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") @@ -29,15 +29,17 @@ option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binarie option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) # Apps and tools +option(BUILD_BSATOOL "build BSA extractor" OFF) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) +option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) # Sound source selection -option(USE_FFMPEG "use ffmpeg for sound" OFF) -option(USE_AUDIERE "use audiere for sound" OFF) +option(USE_FFMPEG "use ffmpeg for sound" ON) +option(USE_AUDIERE "use audiere for sound" ON) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment @@ -66,35 +68,6 @@ endif() # We probably support older versions than this. cmake_minimum_required(VERSION 2.6) -# -# Pre-built binaries being used? -# -IF(EXISTS "${CMAKE_SOURCE_DIR}/prebuilt/vc100-mt-gd/ogre_1_7_1") - set(PREBUILT_DIR "${CMAKE_SOURCE_DIR}/prebuilt/vc100-mt-gd") - message (STATUS "OpenMW pre-built binaries found at ${PREBUILT_DIR}.") - - SET(ENV{OGRE_HOME} "${PREBUILT_DIR}/ogre_1_7_1") - - SET(ENV{BOOST_ROOT} "${PREBUILT_DIR}/boost_1_42_0") - set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_MULTITHREADED ON) - set(ENV{BOOST_INCLUDEDIR} "${BOOST_ROOT}/include") - set(ENV{BOOST_LIBRARYDIR} "${BOOST_ROOT}/lib") - - set(ENV{FREETYPE_DIR} "${PREBUILT_DIR}/freetype-2.3.5-1") - - set(USE_MPG123 OFF) - set(USE_AUDIERE ON) - set(AUDIERE_INCLUDE_DIR "${PREBUILT_DIR}/audiere-1.9.4/include") - set(AUDIERE_LIBRARY "${PREBUILT_DIR}/audiere-1.9.4/lib/audiere.lib") - - set(ENV{OPENALDIR} "${PREBUILT_DIR}/OpenAL 1.1 SDK") - - set(BULLET_ROOT "${PREBUILT_DIR}/bullet") -ELSE() - message (STATUS "OpenMW pre-built binaries not found. Using standard locations.") -ENDIF() - # source directory: libs set(LIBDIR ${CMAKE_SOURCE_DIR}/libs) @@ -102,8 +75,6 @@ set(LIBDIR ${CMAKE_SOURCE_DIR}/libs) set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/renderer.cpp ${LIBDIR}/openengine/ogre/fader.cpp - ${LIBDIR}/openengine/ogre/imagerotate.cpp - ${LIBDIR}/openengine/ogre/atlas.cpp ${LIBDIR}/openengine/ogre/selectionbuffer.cpp ) set(OENGINE_GUI @@ -123,8 +94,6 @@ set(OENGINE_BULLET ${LIBDIR}/openengine/bullet/physic.hpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.h - ${LIBDIR}/openengine/bullet/pmove.cpp - ${LIBDIR}/openengine/bullet/pmove.h ${LIBDIR}/openengine/bullet/trace.cpp ${LIBDIR}/openengine/bullet/trace.h @@ -137,30 +106,54 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup +set(GOT_SOUND_INPUT 0) set(SOUND_INPUT_INCLUDES "") set(SOUND_INPUT_LIBRARY "") set(SOUND_DEFINE "") if (USE_FFMPEG) - find_package(FFMPEG REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) + find_package(FFmpeg) + if (FFMPEG_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(GOT_SOUND_INPUT 1) + endif (FFMPEG_FOUND) endif (USE_FFMPEG) -if (USE_AUDIERE) - find_package(Audiere REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) -endif (USE_AUDIERE) +if (USE_AUDIERE AND NOT GOT_SOUND_INPUT) + find_package(Audiere) + if (AUDIERE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) + set(GOT_SOUND_INPUT 1) + endif (AUDIERE_FOUND) +endif (USE_AUDIERE AND NOT GOT_SOUND_INPUT) -if (USE_MPG123) +if (USE_MPG123 AND NOT GOT_SOUND_INPUT) find_package(MPG123 REQUIRED) find_package(SNDFILE REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) -endif (USE_MPG123) + if (MPG123_FOUND AND SNDFILE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) + set(GOT_SOUND_INPUT 1) + endif (MPG123_FOUND AND SNDFILE_FOUND) +endif (USE_MPG123 AND NOT GOT_SOUND_INPUT) + +if (NOT GOT_SOUND_INPUT) + message(WARNING "--------------------") + message(WARNING "Failed to find any sound input packages") + message(WARNING "--------------------") +endif (NOT GOT_SOUND_INPUT) + +if (NOT FFMPEG_FOUND) + message(WARNING "--------------------") + message(WARNING "FFmpeg not found, video playback will be disabled") + message(WARNING "--------------------") +endif (NOT FFMPEG_FOUND) + # Platform specific if (WIN32) @@ -304,14 +297,14 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg.install") -if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") +if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") endif() # Compiler settings if (CMAKE_COMPILER_IS_GNUCC) - add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder -std=c++0x -pedantic) + add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder -std=c++98 -pedantic -Wno-long-long) # Silence warnings in OGRE headers. Remove once OGRE got fixed! add_definitions (-Wno-ignored-qualifiers) @@ -360,7 +353,7 @@ if(DPKG_PROGRAM) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -385,9 +378,8 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/readme.txt" "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" - "${OpenMW_SOURCE_DIR}/Bitstream Vera License.txt" + "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" - "${OpenMW_BINARY_DIR}/launcher.qss" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" @@ -400,7 +392,7 @@ if(WIN32) 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_MINO}) + SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher") SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") @@ -411,7 +403,7 @@ if(WIN32) SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/readme.txt") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") - SET(CPACK_NSIS_DISPLAY_NAME "OpenMW") + SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}") SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe") @@ -455,6 +447,10 @@ add_subdirectory (components) # Apps and tools add_subdirectory( apps/openmw ) +if (BUILD_BSATOOL) + add_subdirectory( apps/bsatool ) +endif() + if (BUILD_ESMTOOL) add_subdirectory( apps/esmtool ) endif() @@ -467,6 +463,10 @@ if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() +if (BUILD_OPENCS) + add_subdirectory (apps/opencs) +endif() + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) @@ -497,7 +497,7 @@ if (WIN32) # Warnings that aren't enabled normally and don't need to be enabled # They're unneeded and sometimes completely retarded warnings that /Wall enables # Not going to bother commenting them as they tend to warn on every standard library files - 4061 4263 4264 4266 4350 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946 + 4061 4263 4264 4266 4350 4371 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946 # Warnings that are thrown on standard libraries and not OpenMW 4347 # Non-template function with same name and parameter count as template function @@ -508,6 +508,11 @@ if (WIN32) 4986 # Undocumented warning that occurs in the crtdbg.h file 4996 # Function was declared deprecated + # cause by ogre extensivly + 4193 # #pragma warning(pop) : no matching '#pragma warning(push)' + 4251 # class 'XXXX' needs to have dll-interface to be used by clients of class 'YYYY' + 4275 # non dll-interface struct 'XXXX' used as base for dll-interface class 'YYYY' + # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type 4100 # Unreferenced formal parameter (-Wunused-parameter) @@ -525,12 +530,19 @@ if (WIN32) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) + set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${WARNINGS}) set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS}) if (BUILD_LAUNCHER) set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) endif (BUILD_LAUNCHER) set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS}) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + if (BUILD_BSATOOL) + set_target_properties(bsatool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_BSATOOL) + if (BUILD_ESMTOOL) + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_ESMTOOL) endif(MSVC) # Same for MinGW @@ -561,8 +573,6 @@ if (APPLE) install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) @@ -651,9 +661,21 @@ if (NOT WIN32 AND NOT DPKG_PROGRAM AND NOT APPLE) # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_LAUNCHER) + IF(BUILD_BSATOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BSATOOL) + IF(BUILD_ESMTOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_ESMTOOL) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENCS) # Install icon and .desktop INSTALL(FILES "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.png" DESTINATION "${ICONDIR}") @@ -667,5 +689,4 @@ if (NOT WIN32 AND NOT DPKG_PROGRAM AND NOT APPLE) # Install resources INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" ) - INSTALL(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${DATADIR}/resources" ) endif(NOT WIN32 AND NOT DPKG_PROGRAM AND NOT APPLE) diff --git a/DejaVu Font License.txt b/DejaVu Font License.txt new file mode 100644 index 000000000..254e2cc42 --- /dev/null +++ b/DejaVu Font License.txt @@ -0,0 +1,99 @@ +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words "Bitstream" or the word +"Vera". + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the "Bitstream +Vera" names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +associated documentation files (the "Font Software"), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $ diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt new file mode 100644 index 000000000..3f1988a70 --- /dev/null +++ b/apps/bsatool/CMakeLists.txt @@ -0,0 +1,19 @@ +set(BSATOOL + bsatool.cpp +) +source_group(apps\\bsatool FILES ${BSATOOL}) + +# Main executable +add_executable(bsatool + ${BSATOOL} +) + +target_link_libraries(bsatool + ${Boost_LIBRARIES} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(bsatool gcov) +endif() diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp new file mode 100644 index 000000000..e6fcc2567 --- /dev/null +++ b/apps/bsatool/bsatool.cpp @@ -0,0 +1,288 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#define BSATOOL_VERSION 1.1 + +// Create local aliases for brevity +namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; + +struct Arguments +{ + std::string mode; + std::string filename; + std::string extractfile; + std::string outdir; + + bool longformat; + bool fullpath; +}; + +void replaceAll(std::string& str, const std::string& needle, const std::string& substitute) +{ + int pos = str.find(needle); + while(pos != -1) + { + str.replace(pos, needle.size(), substitute); + pos = str.find(needle); + } +} + +bool parseOptions (int argc, char** argv, Arguments &info) +{ + bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n" + "Usages:\n" + " bsatool list [-l] archivefile\n" + " List the files presents in the input archive.\n\n" + " bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n" + " Extract a file from the input archive.\n\n" + " bsatool extractall archivefile [output_directory]\n" + " Extract all files from the input archive.\n\n" + "Allowed options"); + + desc.add_options() + ("help,h", "print help message.") + ("version,v", "print version information and quit.") + ("long,l", "Include extra information in archive listing.") + ("full-path,f", "Create diretory hierarchy on file extraction " + "(always true for extractall).") + ; + + // input-file is hidden and used as a positional argument + bpo::options_description hidden("Hidden Options"); + + hidden.add_options() + ( "mode,m", bpo::value(), "bsatool mode") + ( "input-file,i", bpo::value< std::vector >(), "input file") + ; + + bpo::positional_options_description p; + p.add("mode", 1).add("input-file", 3); + + // there might be a better way to do this + bpo::options_description all; + all.add(desc).add(hidden); + + bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) + .options(all).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch(std::exception &e) + { + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" + << desc << std::endl; + return false; + } + + bpo::notify(variables); + + if (variables.count ("help")) + { + std::cout << desc << std::endl; + return false; + } + if (variables.count ("version")) + { + std::cout << "BSATool version " << BSATOOL_VERSION << std::endl; + return false; + } + if (!variables.count("mode")) + { + std::cout << "ERROR: no mode specified!\n\n" + << desc << std::endl; + return false; + } + + info.mode = variables["mode"].as(); + if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall")) + { + std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" + << desc << std::endl; + return false; + } + + if (!variables.count("input-file")) + { + std::cout << "\nERROR: missing BSA archive\n\n" + << desc << std::endl; + return false; + } + info.filename = variables["input-file"].as< std::vector >()[0]; + + // Default output to the working directory + info.outdir = "."; + + if (info.mode == "extract") + { + if (variables["input-file"].as< std::vector >().size() < 2) + { + std::cout << "\nERROR: file to extract unspecified\n\n" + << desc << std::endl; + return false; + } + if (variables["input-file"].as< std::vector >().size() > 1) + info.extractfile = variables["input-file"].as< std::vector >()[1]; + if (variables["input-file"].as< std::vector >().size() > 2) + info.outdir = variables["input-file"].as< std::vector >()[2]; + } + else if (variables["input-file"].as< std::vector >().size() > 1) + info.outdir = variables["input-file"].as< std::vector >()[1]; + + info.longformat = variables.count("long"); + info.fullpath = variables.count("full-path"); + + return true; +} + +int list(Bsa::BSAFile& bsa, Arguments& info); +int extract(Bsa::BSAFile& bsa, Arguments& info); +int extractAll(Bsa::BSAFile& bsa, Arguments& info); + +int main(int argc, char** argv) +{ + Arguments info; + if(!parseOptions (argc, argv, info)) + return 1; + + // Open file + Bsa::BSAFile bsa; + try + { + bsa.open(info.filename); + } + catch(std::exception &e) + { + std::cout << "ERROR reading BSA archive '" << info.filename + << "'\nDetails:\n" << e.what() << std::endl; + return 2; + } + + if (info.mode == "list") + return list(bsa, info); + else if (info.mode == "extract") + return extract(bsa, info); + else if (info.mode == "extractall") + return extractAll(bsa, info); + else + { + std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; + return 1; + } +} + +int list(Bsa::BSAFile& bsa, Arguments& info) +{ + // List all files + const Bsa::BSAFile::FileList &files = bsa.getList(); + for(int i=0; igetAsString().c_str(), data->size()); + out.close(); + + return 0; +} + +int extractAll(Bsa::BSAFile& bsa, Arguments& info) +{ + // Get the list of files present in the archive + Bsa::BSAFile::FileList list = bsa.getList(); + + // Iter on the list + for(Bsa::BSAFile::FileList::iterator it = list.begin(); it != list.end(); ++it) { + const char* archivePath = it->name; + + std::string extractPath (archivePath); + replaceAll(extractPath, "\\", "/"); + + // Get the target path (the path the file will be extracted to) + bfs::path target (info.outdir); + target /= extractPath; + + // Create the directory hierarchy + bfs::create_directories(target.parent_path()); + + bfs::file_status s = bfs::status(target.parent_path()); + if (!bfs::is_directory(s)) + { + std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; + return 3; + } + + // Get a stream for the file to extract + // (inefficient because getFile iter on the list again) + Ogre::DataStreamPtr data = bsa.getFile(archivePath); + bfs::ofstream out(target, std::ios::binary); + + // Write the file to disk + std::cout << "Extracting " << target << std::endl; + out.write(data->getAsString().c_str(), data->size()); + out.close(); + } + + return 0; +} diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 4f6d9dbfc..3c9476d7a 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -59,7 +59,7 @@ struct Arguments std::string outname; std::vector types; - + ESMData data; ESM::ESMReader reader; ESM::ESMWriter writer; @@ -74,7 +74,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) ("version,v", "print version information and quit.") ("raw,r", "Show an unformatted list of all records and subrecords.") // The intention is that this option would interact better - // with other modes including clone, dump, and raw. + // with other modes including clone, dump, and raw. ("type,t", bpo::value< std::vector >(), "Show only records of this type (four character record code). May " "be specified multiple times. Only affects dump mode.") @@ -165,23 +165,12 @@ bool parseOptions (int argc, char** argv, Arguments &info) // Font encoding settings info.encoding = variables["encoding"].as(); - if (info.encoding == "win1250") + if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252") { - std::cout << "Using Central and Eastern European font encoding." << std::endl; - } - else if (info.encoding == "win1251") - { - std::cout << "Using Cyrillic font encoding." << std::endl; - } - else - { - if(info.encoding != "win1252") - { - std::cout << info.encoding << " is not a valid encoding option." << std::endl; - info.encoding = "win1252"; - } - std::cout << "Using default (English) font encoding." << std::endl; + std::cout << info.encoding << " is not a valid encoding option." << std::endl; + info.encoding = "win1252"; } + std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; return true; } @@ -220,7 +209,10 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) bool save = (info.mode == "clone"); // Skip back to the beginning of the reference list - cell.restore(esm); + // FIXME: Changes to the references backend required to support multiple plugins have + // almost certainly broken this following line. I'll leave it as is for now, so that + // the compiler does not complain. + cell.restore(esm, 0); // Loop through all the references ESM::CellRef ref; @@ -262,7 +254,8 @@ void printRaw(ESM::ESMReader &esm) int load(Arguments& info) { ESM::ESMReader& esm = info.reader; - esm.setEncoding(info.encoding); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); std::string filename = info.filename; std::cout << "Loading file: " << filename << std::endl; @@ -321,7 +314,7 @@ int load(Arguments& info) if (info.types.size() > 0) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), + match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == info.types.end()) interested = false; } @@ -425,14 +418,15 @@ int clone(Arguments& info) if (++i % 3 == 0) std::cout << std::endl; } - + if (i % 3 != 0) std::cout << std::endl; std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl; ESM::ESMWriter& esm = info.writer; - esm.setEncoding(info.encoding); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); esm.setAuthor(info.data.author); esm.setDescription(info.data.description); esm.setVersion(info.data.version); @@ -450,7 +444,7 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - + name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); @@ -485,7 +479,7 @@ int clone(Arguments& info) std::cerr << "\r" << perc << "%"; } } - + std::cout << "\rDone!" << std::endl; esm.close(); @@ -513,7 +507,7 @@ int comp(Arguments& info) fileOne.encoding = info.encoding; fileTwo.encoding = info.encoding; - + fileOne.filename = info.filename; fileTwo.filename = info.outname; @@ -534,9 +528,9 @@ int comp(Arguments& info) std::cout << "Not equal, different amount of records." << std::endl; return 1; } - - - + + + return 0; } diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 9fb233237..f08c31003 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -16,7 +16,7 @@ #include #include -std::string bodyPartLabel(char idx) +std::string bodyPartLabel(int idx) { const char *bodyPartLabels[] = { "Head", @@ -47,14 +47,14 @@ std::string bodyPartLabel(char idx) "Weapon", "Tail" }; - - if ((int)idx >= 0 && (int)(idx) <= 26) - return bodyPartLabels[(int)(idx)]; + + if (idx >= 0 && idx <= 26) + return bodyPartLabels[idx]; else return "Invalid"; } -std::string meshPartLabel(char idx) +std::string meshPartLabel(int idx) { const char *meshPartLabels[] = { "Head", @@ -73,25 +73,25 @@ std::string meshPartLabel(char idx) "Clavicle", "Tail" }; - - if ((int)(idx) >= 0 && (int)(idx) <= ESM::BodyPart::MP_Tail) - return meshPartLabels[(int)(idx)]; + + if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail) + return meshPartLabels[idx]; else return "Invalid"; } -std::string meshTypeLabel(char idx) +std::string meshTypeLabel(int idx) { const char *meshTypeLabels[] = { "Skin", "Clothing", "Armor" }; - - if ((int)(idx) >= 0 && (int)(idx) <= ESM::BodyPart::MT_Armor) - return meshTypeLabels[(int)(idx)]; + + if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor) + return meshTypeLabels[idx]; else - return "Invalid"; + return "Invalid"; } std::string clothingTypeLabel(int idx) @@ -108,7 +108,7 @@ std::string clothingTypeLabel(int idx) "Ring", "Amulet" }; - + if (idx >= 0 && idx <= 9) return clothingTypeLabels[idx]; else diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index 7f1ff7986..48d7b249b 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -3,9 +3,9 @@ #include -std::string bodyPartLabel(char idx); -std::string meshPartLabel(char idx); -std::string meshTypeLabel(char idx); +std::string bodyPartLabel(int idx); +std::string meshPartLabel(int idx); +std::string meshTypeLabel(int idx); std::string clothingTypeLabel(int idx); std::string armorTypeLabel(int idx); std::string dialogTypeLabel(int idx); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b5f97e979..38fddd6b9 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -112,14 +112,11 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) case '5': oper_str = ">="; break; } - std::string value_str = "??"; - if (ss.mType == ESM::VT_Int) - value_str = str(boost::format("%d") % ss.mI); - else if (ss.mType == ESM::VT_Float) - value_str = str(boost::format("%f") % ss.mF); + std::ostringstream stream; + stream << ss.mValue; std::string result = str(boost::format("%-12s %-32s %2s %s") - % type_str % func_str % oper_str % value_str); + % type_str % func_str % oper_str % stream.str()); return result; } @@ -713,32 +710,13 @@ void Record::print() template<> void Record::print() { - // nothing to print (well, nothing that's correct anyway) - std::cout << " Type: " << mData.mType << std::endl; - std::cout << " Value: " << mData.mValue << std::endl; + std::cout << " " << mData.mValue << std::endl; } template<> void Record::print() { - std::cout << " Value: "; - switch (mData.mType) { - case ESM::VT_String: - std::cout << "'" << mData.mStr << "' (std::string)"; - break; - - case ESM::VT_Float: - std::cout << mData.mF << " (float)"; - break; - - case ESM::VT_Int: - std::cout << mData.mI << " (int)"; - break; - - default: - std::cout << "unknown type"; - } - std::cout << "\n Dirty: " << mData.mDirty << std::endl; + std::cout << " " << mData.mValue << std::endl; } template<> diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 09beaf59d..a63898c0e 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -5,14 +5,11 @@ set(LAUNCHER maindialog.cpp playpage.cpp - model/datafilesmodel.cpp - model/modelitem.cpp - model/esm/esmfile.cpp + settings/gamesettings.cpp + settings/graphicssettings.cpp + settings/launchersettings.cpp - utils/filedialog.cpp - utils/naturalsort.cpp - utils/lineedit.cpp - utils/profilescombobox.cpp + utils/checkablemessagebox.cpp utils/textinputdialog.cpp launcher.rc @@ -24,14 +21,12 @@ set(LAUNCHER_HEADER maindialog.hpp playpage.hpp - model/datafilesmodel.hpp - model/modelitem.hpp - model/esm/esmfile.hpp + settings/gamesettings.hpp + settings/graphicssettings.hpp + settings/launchersettings.hpp + settings/settingsbase.hpp - utils/lineedit.hpp - utils/filedialog.hpp - utils/naturalsort.hpp - utils/profilescombobox.hpp + utils/checkablemessagebox.hpp utils/textinputdialog.hpp ) @@ -43,17 +38,18 @@ set(LAUNCHER_HEADER_MOC maindialog.hpp playpage.hpp - model/datafilesmodel.hpp - model/modelitem.hpp - model/esm/esmfile.hpp - - utils/lineedit.hpp - utils/filedialog.hpp - utils/profilescombobox.hpp + utils/checkablemessagebox.hpp utils/textinputdialog.hpp ) -source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER} ${LAUNCHER_HEADER_MOC}) +set(LAUNCHER_UI + ../../files/ui/datafilespage.ui + ../../files/ui/graphicspage.ui + ../../files/ui/mainwindow.ui + ../../files/ui/playpage.ui +) + +source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) find_package(Qt4 REQUIRED) set(QT_USE_QTGUI 1) @@ -64,10 +60,12 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -QT4_ADD_RESOURCES(RCC_SRCS resources.qrc) +QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) +QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include(${QT_USE_FILE}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Main executable IF(OGRE_STATIC) @@ -82,8 +80,10 @@ ENDIF(OGRE_STATIC) add_executable(omwlauncher ${GUI_TYPE} ${LAUNCHER} + ${LAUNCHER_HEADER} ${RCC_SRCS} ${MOC_SRCS} + ${UI_HDRS} ) target_link_libraries(omwlauncher @@ -98,18 +98,6 @@ if(DPKG_PROGRAM) INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) endif() -if (APPLE) - configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss - "${APP_BUNDLE_DIR}/../launcher.qss") -else() - configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources/launcher.qss") - - # Fallback in case getGlobalDataPath does not point to resources - configure_file(${CMAKE_SOURCE_DIR}/files/launcher.qss - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss") -endif() - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(omwlauncher gcov) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6b0539c1d..90cdd2942 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,38 +1,22 @@ -#include - -#include -#include - -#include "model/datafilesmodel.hpp" -#include "model/esm/esmfile.hpp" - -#include "utils/profilescombobox.hpp" -#include "utils/filedialog.hpp" -#include "utils/lineedit.hpp" -#include "utils/naturalsort.hpp" -#include "utils/textinputdialog.hpp" - #include "datafilespage.hpp" -#include -/** - * Workaround for problems with whitespaces in paths in older versions of Boost library - */ -#if (BOOST_VERSION <= 104600) -namespace boost -{ +#include - template<> - inline boost::filesystem::path lexical_cast(const std::string& arg) - { - return boost::filesystem::path(arg); - } +#include -} /* namespace boost */ -#endif /* (BOOST_VERSION <= 104600) */ +#include +#include +#include + +#include +#include +#include + +#include "settings/gamesettings.hpp" +#include "settings/launchersettings.hpp" + +#include "utils/textinputdialog.hpp" -using namespace ESM; -using namespace std; //sort QModelIndexList ascending bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2) @@ -46,138 +30,93 @@ bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2) return index1.row() <= index2.row(); } -DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) - : QWidget(parent) - , mCfgMgr(cfg) +DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) + : mCfgMgr(cfg) + , mGameSettings(gameSettings) + , mLauncherSettings(launcherSettings) + , QWidget(parent) { + setupUi(this); + // Models - mMastersModel = new DataFilesModel(this); - mPluginsModel = new DataFilesModel(this); + mDataFilesModel = new DataFilesModel(this); - mPluginsProxyModel = new QSortFilterProxyModel(); - mPluginsProxyModel->setDynamicSortFilter(true); - mPluginsProxyModel->setSourceModel(mPluginsModel); + mMastersProxyModel = new QSortFilterProxyModel(); + mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm")); + mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mMastersProxyModel->setSourceModel(mDataFilesModel); - // Filter toolbar - QLabel *filterLabel = new QLabel(tr("&Filter:"), this); - LineEdit *filterLineEdit = new LineEdit(this); - filterLabel->setBuddy(filterLineEdit); + mPluginsProxyModel = new PluginsProxyModel(); + mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp")); + mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mPluginsProxyModel->setSourceModel(mDataFilesModel); - QToolBar *filterToolBar = new QToolBar(this); - filterToolBar->setMovable(false); - - // Create a container widget and a layout to get the spacer to work - QWidget *filterWidget = new QWidget(this); - QHBoxLayout *filterLayout = new QHBoxLayout(filterWidget); - QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - filterLayout->addItem(hSpacer1); - filterLayout->addWidget(filterLabel); - filterLayout->addWidget(filterLineEdit); - - filterToolBar->addWidget(filterWidget); + mFilterProxyModel = new QSortFilterProxyModel(); + mFilterProxyModel->setDynamicSortFilter(true); + mFilterProxyModel->setSourceModel(mPluginsProxyModel); QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; - mMastersTable = new QTableView(this); - mMastersTable->setModel(mMastersModel); - mMastersTable->setObjectName("MastersTable"); - mMastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mMastersTable->setSelectionMode(QAbstractItemView::SingleSelection); - mMastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mMastersTable->setAlternatingRowColors(true); - mMastersTable->horizontalHeader()->setStretchLastSection(true); - mMastersTable->horizontalHeader()->hide(); + mastersTable->setModel(mMastersProxyModel); + mastersTable->setObjectName("MastersTable"); + mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); + mastersTable->setSortingEnabled(false); + mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mastersTable->setAlternatingRowColors(true); + mastersTable->horizontalHeader()->setStretchLastSection(true); + mastersTable->horizontalHeader()->hide(); // Set the row height to the size of the checkboxes - mMastersTable->verticalHeader()->setDefaultSectionSize(height); - mMastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mMastersTable->verticalHeader()->hide(); - mMastersTable->setColumnHidden(1, true); - mMastersTable->setColumnHidden(2, true); - mMastersTable->setColumnHidden(3, true); - mMastersTable->setColumnHidden(4, true); - mMastersTable->setColumnHidden(5, true); - mMastersTable->setColumnHidden(6, true); - mMastersTable->setColumnHidden(7, true); - mMastersTable->setColumnHidden(8, true); + mastersTable->verticalHeader()->setDefaultSectionSize(height); + mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + mastersTable->verticalHeader()->hide(); - mPluginsTable = new QTableView(this); - mPluginsTable->setModel(mPluginsProxyModel); - mPluginsTable->setObjectName("PluginsTable"); - mPluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); - mPluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mPluginsTable->setSelectionMode(QAbstractItemView::SingleSelection); - mPluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mPluginsTable->setAlternatingRowColors(true); - mPluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - mPluginsTable->horizontalHeader()->setStretchLastSection(true); - mPluginsTable->horizontalHeader()->hide(); + pluginsTable->setModel(mFilterProxyModel); + pluginsTable->setObjectName("PluginsTable"); + pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); + pluginsTable->setSortingEnabled(false); + pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + pluginsTable->setAlternatingRowColors(true); + pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); + pluginsTable->horizontalHeader()->setStretchLastSection(true); + pluginsTable->horizontalHeader()->hide(); - mPluginsTable->verticalHeader()->setDefaultSectionSize(height); - mPluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mPluginsTable->setColumnHidden(1, true); - mPluginsTable->setColumnHidden(2, true); - mPluginsTable->setColumnHidden(3, true); - mPluginsTable->setColumnHidden(4, true); - mPluginsTable->setColumnHidden(5, true); - mPluginsTable->setColumnHidden(6, true); - mPluginsTable->setColumnHidden(7, true); - mPluginsTable->setColumnHidden(8, true); + pluginsTable->verticalHeader()->setDefaultSectionSize(height); + pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - // Add both tables to a splitter - QSplitter *splitter = new QSplitter(this); - splitter->setOrientation(Qt::Horizontal); - splitter->addWidget(mMastersTable); - splitter->addWidget(mPluginsTable); - - // Adjust the default widget widths inside the splitter + // Adjust the tableview widths inside the splitter QList sizeList; - sizeList << 175 << 200; + sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt(); + sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt(); + splitter->setSizes(sizeList); - // Bottom part with profile options - QLabel *profileLabel = new QLabel(tr("Current Profile: "), this); - - mProfilesComboBox = new ProfilesComboBox(this); - mProfilesComboBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum)); - mProfilesComboBox->setInsertPolicy(QComboBox::NoInsert); - mProfilesComboBox->setDuplicatesEnabled(false); - mProfilesComboBox->setEditEnabled(false); - - mProfileToolBar = new QToolBar(this); - mProfileToolBar->setMovable(false); - mProfileToolBar->setIconSize(QSize(16, 16)); - - mProfileToolBar->addWidget(profileLabel); - mProfileToolBar->addWidget(mProfilesComboBox); - - QVBoxLayout *pageLayout = new QVBoxLayout(this); - - pageLayout->addWidget(filterToolBar); - pageLayout->addWidget(splitter); - pageLayout->addWidget(mProfileToolBar); - // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); + connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mMastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mMastersModel, SIGNAL(checkedItemsChanged(QStringList,QStringList)), mPluginsModel, SLOT(slotcheckedItemsChanged(QStringList,QStringList))); + connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); - connect(mPluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - - connect(mProfilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString))); - connect(mProfilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString))); + connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); createActions(); - setupConfig(); + setupDataFiles(); } void DataFilesPage::createActions() @@ -187,7 +126,7 @@ void DataFilesPage::createActions() refreshAction->setShortcut(QKeySequence(tr("F5"))); connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh())); - // Profile actions + // We can't create actions inside the .ui file mNewProfileAction = new QAction(QIcon::fromTheme("document-new"), tr("&New Profile"), this); mNewProfileAction->setToolTip(tr("New Profile")); mNewProfileAction->setShortcut(QKeySequence(tr("Ctrl+N"))); @@ -195,447 +134,206 @@ void DataFilesPage::createActions() mDeleteProfileAction = new QAction(QIcon::fromTheme("edit-delete"), tr("Delete Profile"), this); mDeleteProfileAction->setToolTip(tr("Delete Profile")); - mDeleteProfileAction->setShortcut(QKeySequence(tr("Delete"))); connect(mDeleteProfileAction, SIGNAL(triggered()), this, SLOT(deleteProfile())); - // Add the newly created actions to the toolbar - mProfileToolBar->addSeparator(); - mProfileToolBar->addAction(mNewProfileAction); - mProfileToolBar->addAction(mDeleteProfileAction); + // Add the newly created actions to the toolbuttons + newProfileButton->setDefaultAction(mNewProfileAction); + deleteProfileButton->setDefaultAction(mDeleteProfileAction); // Context menu actions - mCheckAction = new QAction(tr("Check selected"), this); + mCheckAction = new QAction(tr("Check Selection"), this); connect(mCheckAction, SIGNAL(triggered()), this, SLOT(check())); - mUncheckAction = new QAction(tr("Uncheck selected"), this); + mUncheckAction = new QAction(tr("Uncheck Selection"), this); connect(mUncheckAction, SIGNAL(triggered()), this, SLOT(uncheck())); - // Context menu for the plugins table mContextMenu = new QMenu(this); - mContextMenu->addAction(mCheckAction); mContextMenu->addAction(mUncheckAction); - } -void DataFilesPage::setupConfig() +void DataFilesPage::setupDataFiles() { - // Open our config file - QString config = QString::fromStdString((mCfgMgr.getUserPath() / "launcher.cfg").string()); - mLauncherConfig = new QSettings(config, QSettings::IniFormat); + // Set the encoding to the one found in openmw.cfg or the default + mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); + QStringList paths = mGameSettings.getDataDirs(); + + foreach (const QString &path, paths) { + mDataFilesModel->addFiles(path); } - mLauncherConfig->beginGroup("Profiles"); - QStringList profiles = mLauncherConfig->childGroups(); + QString dataLocal = mGameSettings.getDataLocal(); + if (!dataLocal.isEmpty()) + mDataFilesModel->addFiles(dataLocal); - // Add the profiles to the combobox - foreach (const QString &profile, profiles) { + // Sort by date accessed for now + mDataFilesModel->sort(3); - if (profile.contains(QRegExp("[^a-zA-Z0-9_]"))) - continue; // Profile name contains garbage + QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); + QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + if (!profiles.isEmpty()) + profilesComboBox->addItems(profiles); - qDebug() << "adding " << profile; - mProfilesComboBox->addItem(profile); - } + // Add the current profile if empty + if (profilesComboBox->findText(profile) == -1) + profilesComboBox->addItem(profile); - // Add a default profile - if (mProfilesComboBox->findText(QString("Default")) == -1) { - mProfilesComboBox->addItem(QString("Default")); - } - - QString currentProfile = mLauncherConfig->value("CurrentProfile").toString(); - - if (currentProfile.isEmpty()) { - // No current profile selected - currentProfile = "Default"; - } - - const int currentIndex = mProfilesComboBox->findText(currentProfile); - if (currentIndex != -1) { - // Profile is found - mProfilesComboBox->setCurrentIndex(currentIndex); - } - - mLauncherConfig->endGroup(); -} - - -void DataFilesPage::readConfig() -{ - // Don't read the config if no masters are found - if (mMastersModel->rowCount() < 1) - return; - - QString profile = mProfilesComboBox->currentText(); - - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); - } - - mLauncherConfig->beginGroup("Profiles"); - mLauncherConfig->beginGroup(profile); - - QStringList childKeys = mLauncherConfig->childKeys(); - QStringList plugins; - - // Sort the child keys numerical instead of alphabetically - // i.e. Plugin1, Plugin2 instead of Plugin1, Plugin10 - qSort(childKeys.begin(), childKeys.end(), naturalSortLessThanCI); - - foreach (const QString &key, childKeys) { - const QString keyValue = mLauncherConfig->value(key).toString(); - - if (key.startsWith("Plugin")) { - //QStringList checked = mPluginsModel->checkedItems(); - EsmFile *file = mPluginsModel->findItem(keyValue); - QModelIndex index = mPluginsModel->indexFromItem(file); - - mPluginsModel->setCheckState(index, Qt::Checked); - // Move the row to the top of te view - //mPluginsModel->moveRow(index.row(), checked.count()); - plugins << keyValue; - } - - if (key.startsWith("Master")) { - EsmFile *file = mMastersModel->findItem(keyValue); - mMastersModel->setCheckState(mMastersModel->indexFromItem(file), Qt::Checked); - } - } - - qDebug() << plugins; - - -// // Set the checked item positions -// const QStringList checked = mPluginsModel->checkedItems(); -// for (int i = 0; i < plugins.size(); ++i) { -// EsmFile *file = mPluginsModel->findItem(plugins.at(i)); -// QModelIndex index = mPluginsModel->indexFromItem(file); -// mPluginsModel->moveRow(index.row(), i); -// qDebug() << "Moving: " << plugins.at(i) << " from: " << index.row() << " to: " << i << " count: " << checked.count(); - -// } - - // Iterate over the plugins to set their checkstate and position -// for (int i = 0; i < plugins.size(); ++i) { -// const QString plugin = plugins.at(i); - -// const QList pluginList = mPluginsModel->findItems(plugin); - -// if (!pluginList.isEmpty()) -// { -// foreach (const QStandardItem *currentPlugin, pluginList) { -// mPluginsModel->setData(currentPlugin->index(), Qt::Checked, Qt::CheckStateRole); - -// // Move the plugin to the position specified in the config file -// mPluginsModel->insertRow(i, mPluginsModel->takeRow(currentPlugin->row())); -// } -// } -// } - -} - -bool DataFilesPage::showDataFilesWarning() -{ - - QMessageBox msgBox; - msgBox.setWindowTitle("Error detecting Morrowind installation"); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(tr("
Could not find the Data Files location

\ - The directory containing the data files was not found.

\ - Press \"Browse...\" to specify the location manually.
")); - - QAbstractButton *dirSelectButton = - msgBox.addButton(tr("B&rowse..."), QMessageBox::ActionRole); - - msgBox.exec(); - - if (msgBox.clickedButton() == dirSelectButton) { - - // Show a custom dir selection dialog which only accepts valid dirs - QString selectedDir = FileDialog::getExistingDirectory( - this, tr("Select Data Files Directory"), - QDir::currentPath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - - // Add the user selected data directory - if (!selectedDir.isEmpty()) { - mDataDirs.push_back(Files::PathContainer::value_type(selectedDir.toStdString())); - mCfgMgr.processPaths(mDataDirs); - } else { - // Cancel from within the dir selection dialog - return false; - } + if (profilesComboBox->findText(QString("Default")) == -1) + profilesComboBox->addItem(QString("Default")); + if (profile.isEmpty() || profile == QLatin1String("Default")) { + profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default"))); } else { - // Cancel - return false; + profilesComboBox->setEditEnabled(true); + profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); } - return true; + // We do this here to prevent deletion of profiles when initializing the combobox + connect(profilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString))); + connect(profilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString))); + + loadSettings(); + } -bool DataFilesPage::setupDataFiles() +void DataFilesPage::loadSettings() { - // We use the Configuration Manager to retrieve the configuration values - boost::program_options::variables_map variables; - boost::program_options::options_description desc; - - desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) - ("data-local", boost::program_options::value()->default_value("")) - ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")); - - boost::program_options::notify(variables); - - mCfgMgr.readConfiguration(variables, desc); - - if (variables["data"].empty()) { - if (!showDataFilesWarning()) - return false; - } else { - mDataDirs = Files::PathContainer(variables["data"].as()); - } - - std::string local = variables["data-local"].as(); - if (!local.empty()) { - mDataLocal.push_back(Files::PathContainer::value_type(local)); - } - - mCfgMgr.processPaths(mDataDirs); - mCfgMgr.processPaths(mDataLocal); - - // Second chance to display the warning, the data= entries are invalid - while (mDataDirs.empty()) { - if (!showDataFilesWarning()) - return false; - } - - // Set the charset for reading the esm/esp files - QString encoding = QString::fromStdString(variables["encoding"].as()); - if (!encoding.isEmpty() && encoding != QLatin1String("win1252")) { - mMastersModel->setEncoding(encoding); - mPluginsModel->setEncoding(encoding); - } - - // Add the paths to the respective models - for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) { - QString path = QString::fromStdString(it->string()); - path.remove(QChar('\"')); - mMastersModel->addMasters(path); - mPluginsModel->addPlugins(path); - } - - // Same for the data-local paths - for (Files::PathContainer::iterator it = mDataLocal.begin(); it != mDataLocal.end(); ++it) { - QString path = QString::fromStdString(it->string()); - path.remove(QChar('\"')); - mMastersModel->addMasters(path); - mPluginsModel->addPlugins(path); - } - - mMastersModel->sort(0); - mPluginsModel->sort(0); -// mMastersTable->sortByColumn(3, Qt::AscendingOrder); -// mPluginsTable->sortByColumn(3, Qt::AscendingOrder); - - - readConfig(); - return true; -} - -void DataFilesPage::writeConfig(QString profile) -{ - // Don't overwrite the config if no masters are found - if (mMastersModel->rowCount() < 1) - return; - - QString pathStr = QString::fromStdString(mCfgMgr.getUserPath().string()); - QDir userPath(pathStr); - - if (!userPath.exists()) { - if (!userPath.mkpath(pathStr)) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error creating OpenMW configuration directory"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not create %0

\ - Please make sure you have the right permissions and try again.
").arg(pathStr)); - msgBox.exec(); - - qApp->quit(); - return; - } - } - // Open the OpenMW config as a QFile - QFile file(pathStr.append("openmw.cfg")); - - if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle("Error writing OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0

\ - Please make sure you have the right permissions and try again.
").arg(file.fileName())); - msgBox.exec(); - - qApp->quit(); - return; - } - - QTextStream in(&file); - QByteArray buffer; - - // Remove all previous entries from config - while (!in.atEnd()) { - QString line = in.readLine(); - if (!line.startsWith("master") && - !line.startsWith("plugin") && - !line.startsWith("data") && - !line.startsWith("data-local")) - { - buffer += line += "\n"; - } - } - - file.close(); - - // Now we write back the other config entries - if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error writing OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not write to %0

\ - Please make sure you have the right permissions and try again.
").arg(file.fileName())); - msgBox.exec(); - - qApp->quit(); - return; - } - - if (!buffer.isEmpty()) { - file.write(buffer); - } - - QTextStream gameConfig(&file); - - // First write the list of data dirs - mCfgMgr.processPaths(mDataDirs); - mCfgMgr.processPaths(mDataLocal); - - QString path; - - // data= directories - for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) { - path = QString::fromStdString(it->string()); - path.remove(QChar('\"')); - - // Make sure the string is quoted when it contains spaces - if (path.contains(" ")) { - gameConfig << "data=\"" << path << "\"" << endl; - } else { - gameConfig << "data=" << path << endl; - } - } - - // data-local directory - if (!mDataLocal.empty()) { - path = QString::fromStdString(mDataLocal.front().string()); - path.remove(QChar('\"')); - - if (path.contains(" ")) { - gameConfig << "data-local=\"" << path << "\"" << endl; - } else { - gameConfig << "data-local=" << path << endl; - } - } - - - if (profile.isEmpty()) - profile = mProfilesComboBox->currentText(); + QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); if (profile.isEmpty()) return; - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); + mDataFilesModel->uncheckAll(); + + QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); + QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); + + foreach (const QString &master, masters) { + QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master)); + if (index.isValid()) + mDataFilesModel->setCheckState(index, Qt::Checked); } - mLauncherConfig->beginGroup("Profiles"); - mLauncherConfig->setValue("CurrentProfile", profile); - - // Open the profile-name subgroup - mLauncherConfig->beginGroup(profile); - mLauncherConfig->remove(""); // Clear the subgroup - - // Now write the masters to the configs - const QStringList masters = mMastersModel->checkedItems(); - - // We don't use foreach because we need i - for (int i = 0; i < masters.size(); ++i) { - const QString currentMaster = masters.at(i); - - mLauncherConfig->setValue(QString("Master%0").arg(i), currentMaster); - gameConfig << "master=" << currentMaster << endl; - + foreach (const QString &plugin, plugins) { + QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin)); + if (index.isValid()) + mDataFilesModel->setCheckState(index, Qt::Checked); } - - // And finally write all checked plugins - const QStringList plugins = mPluginsModel->checkedItems(); - - for (int i = 0; i < plugins.size(); ++i) { - const QString currentPlugin = plugins.at(i); - mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin); - gameConfig << "plugin=" << currentPlugin << endl; - } - - file.close(); - mLauncherConfig->endGroup(); - mLauncherConfig->endGroup(); - mLauncherConfig->sync(); } +void DataFilesPage::saveSettings() +{ + if (mDataFilesModel->rowCount() < 1) + return; + + QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + + if (profile.isEmpty()) { + profile = profilesComboBox->currentText(); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); + } + + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); + + mGameSettings.remove(QString("master")); + mGameSettings.remove(QString("plugin")); + + QStringList items = mDataFilesModel->checkedItems(); + + foreach(const QString &item, items) { + + if (item.endsWith(QString(".esm"), Qt::CaseInsensitive)) { + mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item); + mGameSettings.setMultiValue(QString("master"), item); + + } else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) { + mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item); + mGameSettings.setMultiValue(QString("plugin"), item); + } + } + +} void DataFilesPage::newProfile() { if (mNewProfileDialog->exec() == QDialog::Accepted) { - - const QString text = mNewProfileDialog->lineEdit()->text(); - mProfilesComboBox->addItem(text); - - // Copy the currently checked items to cfg - writeConfig(text); - mLauncherConfig->sync(); - - mProfilesComboBox->setCurrentIndex(mProfilesComboBox->findText(text)); + QString profile = mNewProfileDialog->lineEdit()->text(); + profilesComboBox->addItem(profile); + profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); } } void DataFilesPage::updateOkButton(const QString &text) { + // We do this here because we need the profiles combobox text if (text.isEmpty()) { mNewProfileDialog->setOkButtonEnabled(false); return; } - (mProfilesComboBox->findText(text) == -1) + (profilesComboBox->findText(text) == -1) ? mNewProfileDialog->setOkButtonEnabled(true) : mNewProfileDialog->setOkButtonEnabled(false); } +void DataFilesPage::updateSplitter() +{ + // Sigh, update the saved splitter size in settings only when moved + // Since getting mSplitter->sizes() if page is hidden returns invalid values + QList sizes = splitter->sizes(); + + mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0))); + mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1))); +} + +void DataFilesPage::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + mastersTable->setColumnHidden(1, true); + mastersTable->setColumnHidden(2, true); + mastersTable->setColumnHidden(3, true); + mastersTable->setColumnHidden(4, true); + mastersTable->setColumnHidden(5, true); + mastersTable->setColumnHidden(6, true); + mastersTable->setColumnHidden(7, true); + mastersTable->setColumnHidden(8, true); + + pluginsTable->setColumnHidden(1, true); + pluginsTable->setColumnHidden(2, true); + pluginsTable->setColumnHidden(3, true); + pluginsTable->setColumnHidden(4, true); + pluginsTable->setColumnHidden(5, true); + pluginsTable->setColumnHidden(6, true); + pluginsTable->setColumnHidden(7, true); + pluginsTable->setColumnHidden(8, true); +} + +void DataFilesPage::setProfilesComboBoxIndex(int index) +{ + profilesComboBox->setCurrentIndex(index); +} + +void DataFilesPage::slotCurrentIndexChanged(int index) +{ + emit profileChanged(index); +} + +QAbstractItemModel* DataFilesPage::profilesComboBoxModel() +{ + return profilesComboBox->model(); +} + +int DataFilesPage::profilesComboBoxIndex() +{ + return profilesComboBox->currentIndex(); +} + void DataFilesPage::deleteProfile() { - QString profile = mProfilesComboBox->currentText(); + QString profile = profilesComboBox->currentText(); if (profile.isEmpty()) return; @@ -652,75 +350,85 @@ void DataFilesPage::deleteProfile() msgBox.exec(); if (msgBox.clickedButton() == deleteButton) { - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); - } - - mLauncherConfig->beginGroup("Profiles"); - - // Open the profile-name subgroup - mLauncherConfig->beginGroup(profile); - mLauncherConfig->remove(""); // Clear the subgroup - mLauncherConfig->endGroup(); - mLauncherConfig->endGroup(); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); // Remove the profile from the combobox - mProfilesComboBox->removeItem(mProfilesComboBox->findText(profile)); + profilesComboBox->removeItem(profilesComboBox->findText(profile)); } } void DataFilesPage::check() { - // Check the current selection - if (!mPluginsTable->selectionModel()->hasSelection()) { - return; - } + if (pluginsTable->hasFocus()) + setPluginsCheckstates(Qt::Checked); - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); + if (mastersTable->hasFocus()) + setMastersCheckstates(Qt::Checked); - //sort selection ascending because selectedIndexes returns an unsorted list - //qSort(indexes.begin(), indexes.end(), rowSmallerThan); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) - return; - - mPluginsModel->setCheckState(index, Qt::Checked); - } } void DataFilesPage::uncheck() { - // uncheck the current selection - if (!mPluginsTable->selectionModel()->hasSelection()) { - return; - } + if (pluginsTable->hasFocus()) + setPluginsCheckstates(Qt::Unchecked); - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); - - //sort selection ascending because selectedIndexes returns an unsorted list - //qSort(indexes.begin(), indexes.end(), rowSmallerThan); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) - return; - - mPluginsModel->setCheckState(index, Qt::Unchecked); - } + if (mastersTable->hasFocus()) + setMastersCheckstates(Qt::Unchecked); } void DataFilesPage::refresh() { - mPluginsModel->sort(0); - +// mDataFilesModel->sort(0); // Refresh the plugins table - mPluginsTable->scrollToTop(); - writeConfig(); - readConfig(); + pluginsTable->scrollToTop(); } +void DataFilesPage::setMastersCheckstates(Qt::CheckState state) +{ + if (!mastersTable->selectionModel()->hasSelection()) { + return; + } + + QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); + + foreach (const QModelIndex &index, indexes) + { + if (!index.isValid()) + return; + + QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); + + if (!sourceIndex.isValid()) + return; + + mDataFilesModel->setCheckState(sourceIndex, state); + } +} + +void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) +{ + if (!pluginsTable->selectionModel()->hasSelection()) { + return; + } + + QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); + + foreach (const QModelIndex &index, indexes) + { + if (!index.isValid()) + return; + + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( + mFilterProxyModel->mapToSource(index)); + + if (!sourceIndex.isValid()) + return; + + mDataFilesModel->setCheckState(sourceIndex, state); + } +} void DataFilesPage::setCheckState(QModelIndex index) { @@ -733,56 +441,60 @@ void DataFilesPage::setCheckState(QModelIndex index) if (!object) return; - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); - (mPluginsModel->checkState(sourceIndex) == Qt::Checked) - ? mPluginsModel->setCheckState(sourceIndex, Qt::Unchecked) - : mPluginsModel->setCheckState(sourceIndex, Qt::Checked); + if (object->objectName() == QLatin1String("PluginsTable")) { + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( + mFilterProxyModel->mapToSource(index)); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } } if (object->objectName() == QLatin1String("MastersTable")) { - (mMastersModel->checkState(index) == Qt::Checked) - ? mMastersModel->setCheckState(index, Qt::Unchecked) - : mMastersModel->setCheckState(index, Qt::Checked); + QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } } return; - } void DataFilesPage::filterChanged(const QString filter) { QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mPluginsProxyModel->setFilterRegExp(regExp); + mFilterProxyModel->setFilterRegExp(regExp); } void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) { - qDebug() << "Profile is changed from: " << previous << " to " << current; // Prevent the deletion of the default profile if (current == QLatin1String("Default")) { mDeleteProfileAction->setEnabled(false); - mProfilesComboBox->setEditEnabled(false); + profilesComboBox->setEditEnabled(false); } else { mDeleteProfileAction->setEnabled(true); - mProfilesComboBox->setEditEnabled(true); + profilesComboBox->setEditEnabled(true); } - if (!previous.isEmpty()) { - writeConfig(previous); - mLauncherConfig->sync(); - - if (mProfilesComboBox->currentIndex() == -1) - return; - - } else { + if (previous.isEmpty()) return; - } - mMastersModel->uncheckAll(); - mPluginsModel->uncheckAll(); - readConfig(); + if (profilesComboBox->findText(previous) == -1) + return; // Profile was deleted + + // Store the previous profile + mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous); + saveSettings(); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), current); + + loadSettings(); } void DataFilesPage::profileRenamed(const QString &previous, const QString ¤t) @@ -791,54 +503,85 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre return; // Save the new profile name - writeConfig(current); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), current); + saveSettings(); - // Make sure we have no groups open - while (!mLauncherConfig->group().isEmpty()) { - mLauncherConfig->endGroup(); - } + // Remove the old one + mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master")); + mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin")); - mLauncherConfig->beginGroup("Profiles"); + // Remove the profile from the combobox + profilesComboBox->removeItem(profilesComboBox->findText(previous)); - // Open the profile-name subgroup - mLauncherConfig->beginGroup(previous); - mLauncherConfig->remove(""); // Clear the subgroup - mLauncherConfig->endGroup(); - mLauncherConfig->endGroup(); - mLauncherConfig->sync(); + loadSettings(); - // Remove the profile from the combobox - mProfilesComboBox->removeItem(mProfilesComboBox->findText(previous)); - - mMastersModel->uncheckAll(); - mPluginsModel->uncheckAll(); - readConfig(); } void DataFilesPage::showContextMenu(const QPoint &point) { - // Make sure there are plugins in the view - if (!mPluginsTable->selectionModel()->hasSelection()) { + QObject *object = QObject::sender(); + + // Not a signal-slot call + if (!object) return; - } - QPoint globalPos = mPluginsTable->mapToGlobal(point); - - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); - - // Show the check/uncheck actions depending on the state of the selected items - mUncheckAction->setEnabled(false); - mCheckAction->setEnabled(false); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) + if (object->objectName() == QLatin1String("PluginsTable")) { + if (!pluginsTable->selectionModel()->hasSelection()) return; - (mPluginsModel->checkState(index) == Qt::Checked) - ? mUncheckAction->setEnabled(true) - : mCheckAction->setEnabled(true); + QPoint globalPos = pluginsTable->mapToGlobal(point); + QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); + + // Show the check/uncheck actions depending on the state of the selected items + mUncheckAction->setEnabled(false); + mCheckAction->setEnabled(false); + + foreach (const QModelIndex &index, indexes) + { + if (!index.isValid()) + return; + + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( + mFilterProxyModel->mapToSource(index)); + + if (!sourceIndex.isValid()) + return; + + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mUncheckAction->setEnabled(true) + : mCheckAction->setEnabled(true); + } + + // Show menu + mContextMenu->exec(globalPos); } - // Show menu - mContextMenu->exec(globalPos); + if (object->objectName() == QLatin1String("MastersTable")) { + if (!mastersTable->selectionModel()->hasSelection()) + return; + + QPoint globalPos = mastersTable->mapToGlobal(point); + QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); + + // Show the check/uncheck actions depending on the state of the selected items + mUncheckAction->setEnabled(false); + mCheckAction->setEnabled(false); + + foreach (const QModelIndex &index, indexes) + { + if (!index.isValid()) + return; + + QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); + + if (!sourceIndex.isValid()) + return; + + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mUncheckAction->setEnabled(true) + : mCheckAction->setEnabled(true); + } + + mContextMenu->exec(globalPos); + } } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 13668ec30..301abf59b 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -3,93 +3,103 @@ #include #include -#include "utils/profilescombobox.hpp" -#include +#include "ui_datafilespage.h" -class QTableView; class QSortFilterProxyModel; -class QSettings; +class QAbstractItemModel; class QAction; -class QToolBar; class QMenu; -class ProfilesComboBox; -class DataFilesModel; +class DataFilesModel; class TextInputDialog; +class GameSettings; +class LauncherSettings; +class PluginsProxyModel; namespace Files { struct ConfigurationManager; } -class DataFilesPage : public QWidget +class DataFilesPage : public QWidget, private Ui::DataFilesPage { Q_OBJECT public: - DataFilesPage(Files::ConfigurationManager& cfg, QWidget *parent = 0); + DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); - ProfilesComboBox *mProfilesComboBox; + QAbstractItemModel* profilesComboBoxModel(); + int profilesComboBoxIndex(); void writeConfig(QString profile = QString()); - bool showDataFilesWarning(); - bool setupDataFiles(); + void saveSettings(); + +signals: + void profileChanged(int index); public slots: void setCheckState(QModelIndex index); + void setProfilesComboBoxIndex(int index); void filterChanged(const QString filter); void showContextMenu(const QPoint &point); void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &previous, const QString ¤t); void updateOkButton(const QString &text); + void updateSplitter(); + void updateViews(); // Action slots void newProfile(); void deleteProfile(); -// void moveUp(); -// void moveDown(); -// void moveTop(); -// void moveBottom(); void check(); void uncheck(); void refresh(); +private slots: + void slotCurrentIndexChanged(int index); + private: - DataFilesModel *mMastersModel; - DataFilesModel *mPluginsModel; + DataFilesModel *mDataFilesModel; - QSortFilterProxyModel *mPluginsProxyModel; + PluginsProxyModel *mPluginsProxyModel; + QSortFilterProxyModel *mMastersProxyModel; - QTableView *mMastersTable; - QTableView *mPluginsTable; + QSortFilterProxyModel *mFilterProxyModel; - QToolBar *mProfileToolBar; +// QTableView *mMastersTable; +// QTableView *mPluginsTable; + + +// QToolBar *mProfileToolBar; QMenu *mContextMenu; +// QSplitter *mSplitter; QAction *mNewProfileAction; QAction *mDeleteProfileAction; + QAction *mCheckAction; + QAction *mUncheckAction; // QAction *mMoveUpAction; // QAction *mMoveDownAction; // QAction *mMoveTopAction; // QAction *mMoveBottomAction; - QAction *mCheckAction; - QAction *mUncheckAction; Files::ConfigurationManager &mCfgMgr; - Files::PathContainer mDataDirs; - Files::PathContainer mDataLocal; - QSettings *mLauncherConfig; + GameSettings &mGameSettings; + LauncherSettings &mLauncherSettings; TextInputDialog *mNewProfileDialog; -// const QStringList checkedPlugins(); -// const QStringList selectedMasters(); + void setMastersCheckstates(Qt::CheckState state); + void setPluginsCheckstates(Qt::CheckState state); void createActions(); + void setupDataFiles(); void setupConfig(); void readConfig(); + void loadSettings(); + }; #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 2c4f3430c..3caae2c36 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,17 +1,17 @@ +#include "graphicspage.hpp" + #include #include #include -#include #include #include -#include -#include "utils/naturalsort.hpp" +#include -#include "graphicspage.hpp" +#include "settings/graphicssettings.hpp" QString getAspect(int x, int y) { @@ -25,52 +25,22 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) - : QWidget(parent) - , mCfgMgr(cfg) +GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) + : mCfgMgr(cfg) + , mGraphicsSettings(graphicsSetting) + , QWidget(parent) { - QGroupBox *rendererGroup = new QGroupBox(tr("Renderer"), this); + setupUi(this); - QLabel *rendererLabel = new QLabel(tr("Rendering Subsystem:"), rendererGroup); - mRendererComboBox = new QComboBox(rendererGroup); + // Set the maximum res we can set in windowed mode + QRect res = QApplication::desktop()->screenGeometry(); + customWidthSpinBox->setMaximum(res.width()); + customHeightSpinBox->setMaximum(res.height()); - // Layout for the combobox and label - QGridLayout *renderSystemLayout = new QGridLayout(); - renderSystemLayout->addWidget(rendererLabel, 0, 0, 1, 1); - renderSystemLayout->addWidget(mRendererComboBox, 0, 1, 1, 1); + connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&))); + connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int))); + connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool))); - // Display - QGroupBox *displayGroup = new QGroupBox(tr("Display"), this); - - mVSyncCheckBox = new QCheckBox(tr("Vertical Sync"), displayGroup); - mFullScreenCheckBox = new QCheckBox(tr("Full Screen"), displayGroup); - - QLabel *antiAliasingLabel = new QLabel(tr("Antialiasing:"), displayGroup); - QLabel *resolutionLabel = new QLabel(tr("Resolution:"), displayGroup); - - mResolutionComboBox = new QComboBox(displayGroup); - mAntiAliasingComboBox = new QComboBox(displayGroup); - - QVBoxLayout *rendererGroupLayout = new QVBoxLayout(rendererGroup); - rendererGroupLayout->addLayout(renderSystemLayout); - - QGridLayout *displayGroupLayout = new QGridLayout(displayGroup); - displayGroupLayout->addWidget(mVSyncCheckBox, 0, 0, 1, 1); - displayGroupLayout->addWidget(mFullScreenCheckBox, 1, 0, 1, 1); - displayGroupLayout->addWidget(antiAliasingLabel, 2, 0, 1, 1); - displayGroupLayout->addWidget(mAntiAliasingComboBox, 2, 1, 1, 1); - displayGroupLayout->addWidget(resolutionLabel, 3, 0, 1, 1); - displayGroupLayout->addWidget(mResolutionComboBox, 3, 1, 1, 1); - - // Layout for the whole page - QVBoxLayout *pageLayout = new QVBoxLayout(this); - QSpacerItem *vSpacer1 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Expanding); - - pageLayout->addWidget(rendererGroup); - pageLayout->addWidget(displayGroup); - pageLayout->addItem(vSpacer1); - - connect(mRendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&))); } bool GraphicsPage::setupOgre() @@ -117,11 +87,11 @@ bool GraphicsPage::setupOgre() #endif } - boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); - - pluginDir = absPluginPath.string(); + QDir dir(QString::fromStdString(pluginDir)); + pluginDir = dir.absolutePath().toStdString(); Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mOgre); Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); #ifdef ENABLE_PLUGIN_GL @@ -138,7 +108,7 @@ bool GraphicsPage::setupOgre() for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) { mSelectedRenderSystem = *r; - mRendererComboBox->addItem((*r)->getName().c_str()); + rendererComboBox->addItem((*r)->getName().c_str()); } QString openGLName = QString("OpenGL Rendering Subsystem"); @@ -154,71 +124,85 @@ bool GraphicsPage::setupOgre() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
Could not select a valid render system

\ - Please make sure the plugins.cfg file exists and contains a valid rendering plugin.
")); + Please make sure the plugins.cfg file exists and contains a valid rendering plugin.
")); msgBox.exec(); - return false; } // Now fill the GUI elements - int index = mRendererComboBox->findText(QString::fromStdString(Settings::Manager::getString("render system", "Video"))); - + int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system"))); if ( index != -1) { - mRendererComboBox->setCurrentIndex(index); - } - else - { + rendererComboBox->setCurrentIndex(index); + } else { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - mRendererComboBox->setCurrentIndex(mRendererComboBox->findText(direct3DName)); + rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName)); #else - mRendererComboBox->setCurrentIndex(mRendererComboBox->findText(openGLName)); + rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName)); #endif } - mAntiAliasingComboBox->clear(); - mResolutionComboBox->clear(); - mAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); - mResolutionComboBox->addItems(getAvailableResolutions(mSelectedRenderSystem)); + antiAliasingComboBox->clear(); + resolutionComboBox->clear(); + antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); + resolutionComboBox->addItems(getAvailableResolutions(mSelectedRenderSystem)); - readConfig(); + // Load the rest of the values + loadSettings(); return true; } -void GraphicsPage::readConfig() +void GraphicsPage::loadSettings() { - if (Settings::Manager::getBool("vsync", "Video")) - mVSyncCheckBox->setCheckState(Qt::Checked); + if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true")) + vSyncCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("fullscreen", "Video")) - mFullScreenCheckBox->setCheckState(Qt::Checked); + if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true")) + fullScreenCheckBox->setCheckState(Qt::Checked); - int aaIndex = mAntiAliasingComboBox->findText(QString::fromStdString(Settings::Manager::getString("antialiasing", "Video"))); + int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing"))); if (aaIndex != -1) - mAntiAliasingComboBox->setCurrentIndex(aaIndex); + antiAliasingComboBox->setCurrentIndex(aaIndex); - QString resolution = QString::number(Settings::Manager::getInt("resolution x", "Video")); - resolution.append(" x " + QString::number(Settings::Manager::getInt("resolution y", "Video"))); + QString width = mGraphicsSettings.value(QString("Video/resolution x")); + QString height = mGraphicsSettings.value(QString("Video/resolution y")); + QString resolution = width + QString(" x ") + height; - int resIndex = mResolutionComboBox->findText(resolution, Qt::MatchStartsWith); - if (resIndex != -1) - mResolutionComboBox->setCurrentIndex(resIndex); + int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); + + if (resIndex != -1) { + standardRadioButton->toggle(); + resolutionComboBox->setCurrentIndex(resIndex); + } else { + customRadioButton->toggle(); + customWidthSpinBox->setValue(width.toInt()); + customHeightSpinBox->setValue(height.toInt()); + + } } -void GraphicsPage::writeConfig() +void GraphicsPage::saveSettings() { - Settings::Manager::setBool("vsync", "Video", mVSyncCheckBox->checkState()); - Settings::Manager::setBool("fullscreen", "Video", mFullScreenCheckBox->checkState()); - Settings::Manager::setString("antialiasing", "Video", mAntiAliasingComboBox->currentText().toStdString()); - Settings::Manager::setString("render system", "Video", mRendererComboBox->currentText().toStdString()); + vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) + : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); - // Get the current resolution, but with the tabs replaced with a single space - QString resolution = mResolutionComboBox->currentText().simplified(); - QStringList tokens = resolution.split(" ", QString::SkipEmptyParts); + fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true")) + : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false")); - int resX = tokens.at(0).toInt(); - int resY = tokens.at(2).toInt(); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText()); + mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText()); + + + if (standardRadioButton->isChecked()) { + QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); + + if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { + mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1)); + mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2)); + } + } else { + mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value())); + mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value())); + } } QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) @@ -232,16 +216,14 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy { Ogre::StringVector::iterator opt_it; uint idx = 0; - for (opt_it = i->second.possibleValues.begin (); - opt_it != i->second.possibleValues.end (); opt_it++, idx++) - { - if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) - { + for (opt_it = i->second.possibleValues.begin(); + opt_it != i->second.possibleValues.end(); opt_it++, idx++) + { + if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) { result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified(); } } - } // Sort ascending @@ -258,7 +240,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy QStringList GraphicsPage::getAvailableResolutions(Ogre::RenderSystem *renderer) { - QString key ("Video Mode"); + QString key("Video Mode"); QStringList result; uint row = 0; @@ -275,24 +257,26 @@ QStringList GraphicsPage::getAvailableResolutions(Ogre::RenderSystem *renderer) for (opt_it = i->second.possibleValues.begin (); opt_it != i->second.possibleValues.end (); opt_it++, idx++) { - QString qval = QString::fromStdString(*opt_it).simplified(); - // remove extra tokens after the resolution (for example bpp, can be there or not depending on rendersystem) - QStringList tokens = qval.split(" ", QString::SkipEmptyParts); - assert (tokens.size() >= 3); - QString resolutionStr = tokens.at(0) + QString(" x ") + tokens.at(2); + QRegExp resolutionRe(QString("(\\d+) x (\\d+)")); + QString resolution = QString::fromStdString(*opt_it).simplified(); - QString aspect = getAspect(tokens.at(0).toInt(),tokens.at(2).toInt()); + if (resolutionRe.exactMatch(resolution)) { - if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { - resolutionStr.append(tr("\t(Widescreen ") + aspect + ")"); + int width = resolutionRe.cap(1).toInt(); + int height = resolutionRe.cap(2).toInt(); - } else if (aspect == QLatin1String("4:3")) { - resolutionStr.append(tr("\t(Standard 4:3)")); + QString aspect = getAspect(width, height); + + if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { + resolution.append(tr("\t(Wide ") + aspect + ")"); + + } else if (aspect == QLatin1String("4:3")) { + resolution.append(tr("\t(Standard 4:3)")); + } + // do not add duplicate resolutions + if (!result.contains(resolution)) + result.append(resolution); } - - // do not add duplicate resolutions - if (!result.contains(resolutionStr)) - result << resolutionStr; } } @@ -306,9 +290,36 @@ void GraphicsPage::rendererChanged(const QString &renderer) { mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); - mAntiAliasingComboBox->clear(); - mResolutionComboBox->clear(); + antiAliasingComboBox->clear(); + resolutionComboBox->clear(); - mAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); - mResolutionComboBox->addItems(getAvailableResolutions(mSelectedRenderSystem)); + antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); + resolutionComboBox->addItems(getAvailableResolutions(mSelectedRenderSystem)); +} + +void GraphicsPage::slotFullScreenChanged(int state) +{ + if (state == Qt::Checked) { + standardRadioButton->toggle(); + customRadioButton->setEnabled(false); + customWidthSpinBox->setEnabled(false); + customHeightSpinBox->setEnabled(false); + } else { + customRadioButton->setEnabled(true); + customWidthSpinBox->setEnabled(true); + customHeightSpinBox->setEnabled(true); + } +} + +void GraphicsPage::slotStandardToggled(bool checked) +{ + if (checked) { + resolutionComboBox->setEnabled(true); + customWidthSpinBox->setEnabled(false); + customHeightSpinBox->setEnabled(false); + } else { + resolutionComboBox->setEnabled(false); + customWidthSpinBox->setEnabled(true); + customHeightSpinBox->setEnabled(true); + } } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index b8166f672..21039af43 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,8 +5,8 @@ #include #include -#include -#include +//#include +//#include // Static plugin headers #ifdef ENABLE_PLUGIN_GL @@ -16,26 +16,29 @@ # include "OgreD3D9Plugin.h" #endif -class QComboBox; -class QCheckBox; -class QStackedWidget; -class QSettings; +#include "ui_graphicspage.h" + +class GraphicsSettings; namespace Files { struct ConfigurationManager; } -class GraphicsPage : public QWidget +class GraphicsPage : public QWidget, private Ui::GraphicsPage { Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent = 0); + GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + void saveSettings(); bool setupOgre(); - void writeConfig(); public slots: void rendererChanged(const QString &renderer); +private slots: + void slotFullScreenChanged(int state); + void slotStandardToggled(bool checked); + private: Ogre::Root *mOgre; Ogre::RenderSystem *mSelectedRenderSystem; @@ -48,22 +51,14 @@ private: Ogre::D3D9Plugin* mD3D9Plugin; #endif - QComboBox *mRendererComboBox; - - QStackedWidget *mDisplayStackedWidget; - - QComboBox *mAntiAliasingComboBox; - QComboBox *mResolutionComboBox; - QCheckBox *mVSyncCheckBox; - QCheckBox *mFullScreenCheckBox; - Files::ConfigurationManager &mCfgMgr; + GraphicsSettings &mGraphicsSettings; QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); QStringList getAvailableResolutions(Ogre::RenderSystem *renderer); - void createPages(); - void readConfig(); + void loadSettings(); + }; #endif diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 7c4cb5f7e..09da1d615 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,6 +1,6 @@ #include +#include #include -#include #include "maindialog.hpp" @@ -30,14 +30,17 @@ int main(int argc, char *argv[]) QDir::setCurrent(dir.absolutePath()); + // Support non-latin characters + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + MainDialog mainWin; if (mainWin.setup()) { - mainWin.show(); - return app.exec(); + } else { + return 0; } - return 0; + return app.exec(); } diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 674ccdf67..6a3965cc9 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,52 +1,16 @@ +#include "maindialog.hpp" + #include -#include "maindialog.hpp" +#include "utils/checkablemessagebox.hpp" + #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" MainDialog::MainDialog() + : mGameSettings(mCfgMgr) { - QWidget *centralWidget = new QWidget(this); - setCentralWidget(centralWidget); - - mIconWidget = new QListWidget(centralWidget); - mIconWidget->setObjectName("IconWidget"); - mIconWidget->setViewMode(QListView::IconMode); - mIconWidget->setWrapping(false); - mIconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure - mIconWidget->setIconSize(QSize(48, 48)); - mIconWidget->setMovement(QListView::Static); - - mIconWidget->setMinimumWidth(400); - mIconWidget->setFixedHeight(80); - mIconWidget->setSpacing(4); - mIconWidget->setCurrentRow(0); - mIconWidget->setFlow(QListView::LeftToRight); - - QGroupBox *groupBox = new QGroupBox(centralWidget); - QVBoxLayout *groupLayout = new QVBoxLayout(groupBox); - - mPagesWidget = new QStackedWidget(groupBox); - groupLayout->addWidget(mPagesWidget); - - QPushButton *playButton = new QPushButton(tr("Play")); - - QDialogButtonBox *buttonBox = new QDialogButtonBox(centralWidget); - buttonBox->setStandardButtons(QDialogButtonBox::Close); - buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole); - - QVBoxLayout *dialogLayout = new QVBoxLayout(centralWidget); - dialogLayout->addWidget(mIconWidget); - dialogLayout->addWidget(groupBox); - dialogLayout->addWidget(buttonBox); - - setWindowTitle(tr("OpenMW Launcher")); - setWindowIcon(QIcon(":/images/openmw.png")); - // Remove what's this? button - setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); - setMinimumSize(QSize(575, 575)); - // Install the stylesheet font QFile file; QFontDatabase fontDatabase; @@ -56,64 +20,67 @@ MainDialog::MainDialog() // Check if the font is installed if (!fonts.contains("EB Garamond")) { - QString font = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/mygui/EBGaramond-Regular.ttf").string()); - file.setFileName(font); + QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + file.setFileName(font); - if (!file.exists()) { - font = QString::fromStdString((mCfgMgr.getLocalPath() / "resources/mygui/EBGaramond-Regular.ttf").string()); - } + if (!file.exists()) { + font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + } - fontDatabase.addApplicationFont(font); + fontDatabase.addApplicationFont(font); } - // Load the stylesheet - QString config = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/launcher.qss").string()); - file.setFileName(config); + setupUi(this); - if (!file.exists()) { - file.setFileName(QString::fromStdString((mCfgMgr.getLocalPath() / "launcher.qss").string())); - } + iconWidget->setViewMode(QListView::IconMode); + iconWidget->setWrapping(false); + iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure + iconWidget->setIconSize(QSize(48, 48)); + iconWidget->setMovement(QListView::Static); - file.open(QFile::ReadOnly); - QString styleSheet = QLatin1String(file.readAll()); - qApp->setStyleSheet(styleSheet); - file.close(); + iconWidget->setSpacing(4); + iconWidget->setCurrentRow(0); + iconWidget->setFlow(QListView::LeftToRight); + + QPushButton *playButton = new QPushButton(tr("Play")); + buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(play())); + // Remove what's this? button + setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + createIcons(); - createPages(); } void MainDialog::createIcons() { - if (!QIcon::hasThemeIcon("document-new")) { + if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); - } // We create a fallback icon because the default fallback doesn't work QIcon graphicsIcon = QIcon(":/icons/tango/video-display.png"); - QListWidgetItem *playButton = new QListWidgetItem(mIconWidget); + QListWidgetItem *playButton = new QListWidgetItem(iconWidget); playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setText(tr("Play")); playButton->setTextAlignment(Qt::AlignCenter); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *graphicsButton = new QListWidgetItem(mIconWidget); + QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); graphicsButton->setIcon(QIcon::fromTheme("video-display", graphicsIcon)); graphicsButton->setText(tr("Graphics")); graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *dataFilesButton = new QListWidgetItem(mIconWidget); + QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget); dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); dataFilesButton->setText(tr("Data Files")); dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - connect(mIconWidget, + connect(iconWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); @@ -122,77 +89,185 @@ void MainDialog::createIcons() void MainDialog::createPages() { mPlayPage = new PlayPage(this); - mGraphicsPage = new GraphicsPage(mCfgMgr, this); - mDataFilesPage = new DataFilesPage(mCfgMgr, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage - mPlayPage->mProfilesComboBox->setModel(mDataFilesPage->mProfilesComboBox->model()); - mPlayPage->mProfilesComboBox->setCurrentIndex(mDataFilesPage->mProfilesComboBox->currentIndex()); + mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); + mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); // Add the pages to the stacked widget - mPagesWidget->addWidget(mPlayPage); - mPagesWidget->addWidget(mGraphicsPage); - mPagesWidget->addWidget(mDataFilesPage); + pagesWidget->addWidget(mPlayPage); + pagesWidget->addWidget(mGraphicsPage); + pagesWidget->addWidget(mDataFilesPage); // Select the first page - mIconWidget->setCurrentItem(mIconWidget->item(0), QItemSelectionModel::Select); + iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); - connect(mPlayPage->mPlayButton, SIGNAL(clicked()), this, SLOT(play())); + connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); - connect(mPlayPage->mProfilesComboBox, - SIGNAL(currentIndexChanged(int)), - mDataFilesPage->mProfilesComboBox, SLOT(setCurrentIndex(int))); - - connect(mDataFilesPage->mProfilesComboBox, - SIGNAL(currentIndexChanged(int)), - mPlayPage->mProfilesComboBox, SLOT(setCurrentIndex(int))); + connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int))); + connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int))); } +bool MainDialog::showFirstRunDialog() +{ + CheckableMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Morrowind installation detected")); + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); + int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize); + msgBox.setIconPixmap(icon.pixmap(size, size)); + + + QAbstractButton *importerButton = + msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?! + QAbstractButton *skipButton = + msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole); + + Q_UNUSED(skipButton); // Surpress compiler unused warning + + msgBox.setStandardButtons(QDialogButtonBox::NoButton); + + msgBox.setText(tr("
An existing Morrowind installation was detected

\ + Would you like to import settings from Morrowind.ini?
")); + + msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)")); + msgBox.exec(); + + + if (msgBox.clickedButton() == importerButton) { + + QStringList iniPaths; + + foreach (const QString &path, mGameSettings.getDataDirs()) { + QDir dir(path); + dir.setPath(dir.canonicalPath()); // Resolve symlinks + + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } + + if (iniPaths.isEmpty()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error reading Morrowind configuration file")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ + The problem may be due to an incomplete installation of Morrowind.
\ + Reinstalling Morrowind may resolve the problem.")); + msgBox.exec(); + return false; + } + + if (iniPaths.count() > 1) { + // Multiple Morrowind.ini files found + bool ok; + QString path = QInputDialog::getItem(this, tr("Multiple configurations found"), + tr("
There are multiple Morrowind.ini files found.

\ + Please select the one you wish to import from:"), iniPaths, 0, false, &ok); + if (ok && !path.isEmpty()) { + iniPaths.clear(); + iniPaths.append(path); + } else { + // Cancel was clicked TODO: should we abort here? + return false; + } + } + + // Create the file if it doesn't already exist, else the importer will fail + QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg"); + QFile file(path); + + if (!file.exists()) { + if (!file.open(QIODevice::ReadWrite)) { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + if (msgBox.isChecked()) + arguments.append(QString("-g")); + + arguments.append(iniPaths.first()); + arguments.append(path); + + if (!startProgram(QString("mwiniimport"), arguments, false)) + return false; + + // Re-read the game settings + mGameSettings.clear(); + + if (!setupGameSettings()) + return false; + + // Add a new profile + if (msgBox.isChecked()) { + mLauncherSettings.setValue(QString("Profiles/CurrentProfile"), QString("Imported")); + + mLauncherSettings.remove(QString("Profiles/Imported/master")); + mLauncherSettings.remove(QString("Profiles/Imported/plugin")); + + QStringList masters = mGameSettings.values(QString("master")); + QStringList plugins = mGameSettings.values(QString("plugin")); + + foreach (const QString &master, masters) { + mLauncherSettings.setMultiValue(QString("Profiles/Imported/master"), master); + } + + foreach (const QString &plugin, plugins) { + mLauncherSettings.setMultiValue(QString("Profiles/Imported/plugin"), plugin); + } + } + + } + + return true; +} bool MainDialog::setup() { - // Create the settings manager and load default settings file - const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); - const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); - - // prefer local - if (boost::filesystem::exists(localdefault)) { - mSettings.loadDefault(localdefault); - } else if (boost::filesystem::exists(globaldefault)) { - mSettings.loadDefault(globaldefault); - } else { - QMessageBox msgBox; - msgBox.setWindowTitle("Error reading OpenMW configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find %0

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.").arg(QString::fromStdString(globaldefault))); - msgBox.exec(); + if (!setupLauncherSettings()) return false; + + if (!setupGameSettings()) + return false; + + if (!setupGraphicsSettings()) + return false; + + // Check if we need to show the importer + if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) + { + if (!showFirstRunDialog()) + return false; } - // load user settings if they exist, otherwise just load the default settings as user settings - const std::string settingspath = (mCfgMgr.getUserPath() / "settings.cfg").string(); + // Now create the pages as they need the settings + createPages(); - if (boost::filesystem::exists(settingspath)) - mSettings.loadUser(settingspath); - else if (boost::filesystem::exists(localdefault)) - mSettings.loadUser(localdefault); - else if (boost::filesystem::exists(globaldefault)) - mSettings.loadUser(globaldefault); - - // Setup the Graphics page - if (!mGraphicsPage->setupOgre()) { + // Call this so we can exit on Ogre errors before mainwindow is shown + if (!mGraphicsPage->setupOgre()) return false; - } - - // Setup the Data Files page - if (!mDataFilesPage->setupDataFiles()) { - return false; - } + loadSettings(); return true; } @@ -201,89 +276,409 @@ void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) if (!current) current = previous; - mPagesWidget->setCurrentIndex(mIconWidget->row(current)); + pagesWidget->setCurrentIndex(iconWidget->row(current)); +} + +bool MainDialog::setupLauncherSettings() +{ + QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + + QStringList paths; + paths.append(QString("launcher.cfg")); + paths.append(userPath + QString("launcher.cfg")); + + foreach (const QString &path, paths) { + qDebug() << "Loading config file:" << qPrintable(path); + QFile file(path); + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.readFile(stream); + } + file.close(); + } + + return true; +} + +bool MainDialog::setupGameSettings() +{ + QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + + QStringList paths; + paths.append(userPath + QString("openmw.cfg")); + paths.append(QString("openmw.cfg")); + paths.append(globalPath + QString("openmw.cfg")); + + foreach (const QString &path, paths) { + qDebug() << "Loading config file:" << qPrintable(path); + + QFile file(path); + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readFile(stream); + } + file.close(); + } + + QStringList dataDirs; + + // Check if the paths actually contain data files + foreach (const QString path, mGameSettings.getDataDirs()) { + QDir dir(path); + QStringList filters; + filters << "*.esp" << "*.esm"; + + if (!dir.entryList(filters).isEmpty()) + dataDirs.append(path); + } + + if (dataDirs.isEmpty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(QObject::tr("
Could not find the Data Files location

\ + The directory containing the data files was not found.

\ + Press \"Browse...\" to specify the location manually.
")); + + QAbstractButton *dirSelectButton = + msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + + msgBox.exec(); + + QString selectedFile; + if (msgBox.clickedButton() == dirSelectButton) { + selectedFile = QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select master file"), + QDir::currentPath(), + QString(tr("Morrowind master file (*.esm)"))); + } + + if (selectedFile.isEmpty()) + return false; // Cancel was clicked; + + QFileInfo info(selectedFile); + + // Add the new dir to the settings file and to the data dir container + mGameSettings.setValue(QString("data"), info.absolutePath()); + mGameSettings.addDataDir(info.absolutePath()); + } + + return true; +} + +bool MainDialog::setupGraphicsSettings() +{ + QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + + QFile localDefault(QString("settings-default.cfg")); + QFile globalDefault(globalPath + QString("settings-default.cfg")); + + if (!localDefault.exists() && !globalDefault.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not find settings-default.cfg

\ + The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.")); + msgBox.exec(); + return false; + } + + + QStringList paths; + paths.append(globalPath + QString("settings-default.cfg")); + paths.append(QString("settings-default.cfg")); + paths.append(userPath + QString("settings.cfg")); + + foreach (const QString &path, paths) { + qDebug() << "Loading config file:" << qPrintable(path); + QFile file(path); + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGraphicsSettings.readFile(stream); + } + file.close(); + } + + return true; +} + +void MainDialog::loadSettings() +{ + int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); + int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); + + int posX = mLauncherSettings.value(QString("General/MainWindow/posx")).toInt(); + int posY = mLauncherSettings.value(QString("General/MainWindow/posy")).toInt(); + + resize(width, height); + move(posX, posY); +} + +void MainDialog::saveSettings() +{ + QString width = QString::number(this->width()); + QString height = QString::number(this->height()); + + mLauncherSettings.setValue(QString("General/MainWindow/width"), width); + mLauncherSettings.setValue(QString("General/MainWindow/height"), height); + + QString posX = QString::number(this->pos().x()); + QString posY = QString::number(this->pos().y()); + + mLauncherSettings.setValue(QString("General/MainWindow/posx"), posX); + mLauncherSettings.setValue(QString("General/MainWindow/posy"), posY); + + mLauncherSettings.setValue(QString("General/firstrun"), QString("false")); + +} + +void MainDialog::writeSettings() +{ + // Now write all config files + saveSettings(); + mGraphicsPage->saveSettings(); + mDataFilesPage->saveSettings(); + + QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QDir dir(userPath); + + if (!dir.exists()) { + if (!dir.mkpath(userPath)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not create %0

\ + Please make sure you have the right permissions \ + and try again.
").arg(userPath)); + msgBox.exec(); + return; + } + } + + // Game settings + QFile file(userPath + QString("openmw.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return; + } + + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.writeFile(stream); + file.close(); + + // Graphics settings + file.setFileName(userPath + QString("settings.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return; + } + + stream.setDevice(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGraphicsSettings.writeFile(stream); + file.close(); + + // Launcher settings + file.setFileName(userPath + QString("launcher.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing Launcher configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return; + } + + stream.setDevice(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.writeFile(stream); + file.close(); } void MainDialog::closeEvent(QCloseEvent *event) { - // Now write all config files - mDataFilesPage->writeConfig(); - mGraphicsPage->writeConfig(); - - // Save user settings - const std::string settingspath = (mCfgMgr.getUserPath() / "settings.cfg").string(); - mSettings.saveUser(settingspath); - + saveSettings(); + writeSettings(); event->accept(); } void MainDialog::play() { - // First do a write of all the configs, just to be sure - mDataFilesPage->writeConfig(); - mGraphicsPage->writeConfig(); + saveSettings(); + writeSettings(); - // Save user settings - const std::string settingspath = (mCfgMgr.getUserPath() / "settings.cfg").string(); - mSettings.saveUser(settingspath); + // Launch the game detached + startProgram(QString("openmw"), true); + qApp->quit(); +} +bool MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) +{ + QString path = name; #ifdef Q_OS_WIN - QString game = "./openmw.exe"; - QFile file(game); + path.append(QString(".exe")); #elif defined(Q_OS_MAC) QDir dir(QCoreApplication::applicationDirPath()); - QString game = dir.absoluteFilePath("openmw"); - QFile file(game); - game = "\"" + game + "\""; + path = dir.absoluteFilePath(name); #else - QString game = "./openmw"; - QFile file(game); + path.prepend(QString("./")); #endif + QFile file(path); + QProcess process; QFileInfo info(file); if (!file.exists()) { QMessageBox msgBox; - msgBox.setWindowTitle("Error starting OpenMW"); + msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find OpenMW

\ - The OpenMW application is not found.
\ - Please make sure OpenMW is installed correctly and try again.
")); + msgBox.setText(tr("
Could not find %1

\ + The application is not found.
\ + Please make sure OpenMW is installed correctly and try again.
").arg(info.fileName())); msgBox.exec(); - return; + return false; } if (!info.isExecutable()) { QMessageBox msgBox; - msgBox.setWindowTitle("Error starting OpenMW"); + msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start OpenMW

\ - The OpenMW application is not executable.
\ - Please make sure you have the right permissions and try again.
")); + msgBox.setText(tr("
Could not start %1

\ + The application is not executable.
\ + Please make sure you have the right permissions and try again.
").arg(info.fileName())); msgBox.exec(); - return; + return false; } - // Start the game - if (!process.startDetached(game)) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error starting OpenMW"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start OpenMW

\ - An error occurred while starting OpenMW.

\ - Press \"Show Details...\" for more information.
")); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); + // Start the executable + if (detached) { + if (!process.startDetached(path, arguments)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not start %1

\ + An error occurred while starting %1.

\ + Press \"Show Details...\" for more information.
").arg(info.fileName())); + msgBox.setDetailedText(process.errorString()); + msgBox.exec(); - return; + return false; + } } else { - qApp->quit(); - } -} + process.start(path, arguments); + if (!process.waitForFinished()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not start %1

\ + An error occurred while starting %1.

\ + Press \"Show Details...\" for more information.
").arg(info.fileName())); + msgBox.setDetailedText(process.errorString()); + msgBox.exec(); + return false; + } + + if (process.exitCode() != 0) { + QString error(process.readAllStandardError()); + error.append(tr("\nArguments:\n")); + error.append(arguments.join(" ")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Executable %1 returned an error

\ + An error occurred while running %1.

\ + Press \"Show Details...\" for more information.
").arg(info.fileName())); + msgBox.setDetailedText(error); + msgBox.exec(); + + return false; + } + } + + return true; + +} diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index bf98011cc..643206ab6 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -4,7 +4,12 @@ #include #include -#include + +#include "settings/gamesettings.hpp" +#include "settings/graphicssettings.hpp" +#include "settings/launchersettings.hpp" + +#include "ui_mainwindow.h" class QListWidget; class QListWidgetItem; @@ -17,32 +22,46 @@ class PlayPage; class GraphicsPage; class DataFilesPage; -class MainDialog : public QMainWindow +class MainDialog : public QMainWindow, private Ui::MainWindow { Q_OBJECT public: MainDialog(); + bool setup(); + bool showFirstRunDialog(); public slots: void changePage(QListWidgetItem *current, QListWidgetItem *previous); void play(); - bool setup(); private: void createIcons(); void createPages(); - void closeEvent(QCloseEvent *event); - QListWidget *mIconWidget; - QStackedWidget *mPagesWidget; + bool setupLauncherSettings(); + bool setupGameSettings(); + bool setupGraphicsSettings(); + + void loadSettings(); + void saveSettings(); + void writeSettings(); + + inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } + bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); + + void closeEvent(QCloseEvent *event); PlayPage *mPlayPage; GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; Files::ConfigurationManager mCfgMgr; - Settings::Manager mSettings; + + GameSettings mGameSettings; + GraphicsSettings mGraphicsSettings; + LauncherSettings mLauncherSettings; + }; #endif diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index cb993a8fa..27b7846dd 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -1,43 +1,40 @@ -#include - #include "playpage.hpp" +#include + PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { - QWidget *playWidget = new QWidget(this); - playWidget->setObjectName("PlayGroup"); - playWidget->setFixedSize(QSize(425, 375)); - - mPlayButton = new QPushButton(tr("Play"), playWidget); - mPlayButton->setObjectName("PlayButton"); - mPlayButton->setMinimumSize(QSize(200, 50)); - - QLabel *profileLabel = new QLabel(tr("Current Profile:"), playWidget); - profileLabel->setObjectName("ProfileLabel"); + setupUi(this); + // Hacks to get the stylesheet look properly on different platforms QPlastiqueStyle *style = new QPlastiqueStyle; - mProfilesComboBox = new QComboBox(playWidget); - mProfilesComboBox->setObjectName("ProfilesComboBox"); - mProfilesComboBox->setStyle(style); + QFont font = QApplication::font(); + font.setPointSize(12); // Fixes problem with overlapping items - QGridLayout *playLayout = new QGridLayout(playWidget); + profilesComboBox->setStyle(style); + profilesComboBox->setFont(font); - QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - QSpacerItem *hSpacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); + connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); - QSpacerItem *vSpacer1 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); - QSpacerItem *vSpacer2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); +} - playLayout->addWidget(mPlayButton, 1, 1, 1, 1); - playLayout->addWidget(profileLabel, 2, 1, 1, 1); - playLayout->addWidget(mProfilesComboBox, 3, 1, 1, 1); - playLayout->addItem(hSpacer1, 2, 0, 1, 1); - playLayout->addItem(hSpacer2, 2, 2, 1, 1); - playLayout->addItem(vSpacer1, 0, 1, 1, 1); - playLayout->addItem(vSpacer2, 4, 1, 1, 1); +void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model) +{ + profilesComboBox->setModel(model); +} - QHBoxLayout *pageLayout = new QHBoxLayout(this); +void PlayPage::setProfilesComboBoxIndex(int index) +{ + profilesComboBox->setCurrentIndex(index); +} - pageLayout->addWidget(playWidget); +void PlayPage::slotCurrentIndexChanged(int index) +{ + emit profileChanged(index); +} -} \ No newline at end of file +void PlayPage::slotPlayClicked() +{ + emit playButtonClicked(); +} diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index efec6f2b3..4306396bd 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -3,19 +3,33 @@ #include +#include "ui_playpage.h" + class QComboBox; class QPushButton; +class QAbstractItemModel; -class PlayPage : public QWidget +class PlayPage : public QWidget, private Ui::PlayPage { Q_OBJECT public: PlayPage(QWidget *parent = 0); + void setProfilesComboBoxModel(QAbstractItemModel *model); + +signals: + void profileChanged(int index); + void playButtonClicked(); + +public slots: + void setProfilesComboBoxIndex(int index); + +private slots: + void slotCurrentIndexChanged(int index); + void slotPlayClicked(); + - QComboBox *mProfilesComboBox; - QPushButton *mPlayButton; }; -#endif \ No newline at end of file +#endif diff --git a/apps/launcher/resources.qrc b/apps/launcher/resources.qrc deleted file mode 100644 index 2b56f80fd..000000000 --- a/apps/launcher/resources.qrc +++ /dev/null @@ -1,21 +0,0 @@ - - - resources/images/clear.png - resources/images/down.png - resources/images/openmw.png - resources/images/openmw-plugin.png - resources/images/openmw-header.png - resources/images/playpage-background.png - - - resources/icons/tango/index.theme - resources/icons/tango/video-display.png - resources/icons/tango/document-new.png - resources/icons/tango/edit-copy.png - resources/icons/tango/edit-delete.png - resources/icons/tango/go-bottom.png - resources/icons/tango/go-down.png - resources/icons/tango/go-top.png - resources/icons/tango/go-up.png - - diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp new file mode 100644 index 000000000..56c08582f --- /dev/null +++ b/apps/launcher/settings/gamesettings.cpp @@ -0,0 +1,177 @@ +#include "gamesettings.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +#include +/** + * Workaround for problems with whitespaces in paths in older versions of Boost library + */ +#if (BOOST_VERSION <= 104600) +namespace boost +{ + + template<> + inline boost::filesystem::path lexical_cast(const std::string& arg) + { + return boost::filesystem::path(arg); + } + +} /* namespace boost */ +#endif /* (BOOST_VERSION <= 104600) */ + + +GameSettings::GameSettings(Files::ConfigurationManager &cfg) + : mCfgMgr(cfg) +{ +} + +GameSettings::~GameSettings() +{ +} + +void GameSettings::validatePaths() +{ + if (mSettings.isEmpty() || !mDataDirs.isEmpty()) + return; // Don't re-validate paths if they are already parsed + + QStringList paths = mSettings.values(QString("data")); + Files::PathContainer dataDirs; + + foreach (const QString &path, paths) { + dataDirs.push_back(Files::PathContainer::value_type(path.toStdString())); + } + + // Parse the data dirs to convert the tokenized paths + mCfgMgr.processPaths(dataDirs); + mDataDirs.clear(); + + for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { + QString path = QString::fromStdString(it->string()); + path.remove(QChar('\"')); + + QDir dir(path); + if (dir.exists()) + mDataDirs.append(path); + } + + // Do the same for data-local + QString local = mSettings.value(QString("data-local")); + + if (local.isEmpty()) + return; + + dataDirs.clear(); + dataDirs.push_back(Files::PathContainer::value_type(local.toStdString())); + + mCfgMgr.processPaths(dataDirs); + + if (!dataDirs.empty()) { + QString path = QString::fromStdString(dataDirs.front().string()); + path.remove(QChar('\"')); + + QDir dir(path); + if (dir.exists()) + mDataLocal = path; + } +} + +QStringList GameSettings::values(const QString &key, const QStringList &defaultValues) +{ + if (!mSettings.values(key).isEmpty()) + return mSettings.values(key); + return defaultValues; +} + +bool GameSettings::readFile(QTextStream &stream) +{ + QMap cache; + QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + + while (!stream.atEnd()) { + QString line = stream.readLine().simplified(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + if (keyRe.indexIn(line) != -1) { + + QString key = keyRe.cap(1).simplified(); + QString value = keyRe.cap(2).simplified(); + + // Don't remove existing data entries + if (key != QLatin1String("data")) + mSettings.remove(key); + + QStringList values = cache.values(key); + values.append(mSettings.values(key)); + + if (!values.contains(value)) { + cache.insertMulti(key, value); + } + } + } + + if (mSettings.isEmpty()) { + mSettings = cache; // This is the first time we read a file + validatePaths(); + return true; + } + + // Merge the changed keys with those which didn't + mSettings.unite(cache); + validatePaths(); + + return true; +} + +bool GameSettings::writeFile(QTextStream &stream) +{ + // Iterate in reverse order to preserve insertion order + QMapIterator i(mSettings); + i.toBack(); + + while (i.hasPrevious()) { + i.previous(); + + if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin")) + continue; + + // Quote paths with spaces + if (i.key() == QLatin1String("data") + || i.key() == QLatin1String("data-local") + || i.key() == QLatin1String("resources")) + { + if (i.value().contains(QChar(' '))) + { + QString stripped = i.value(); + stripped.remove(QChar('\"')); // Remove quotes + + stream << i.key() << "=\"" << stripped << "\"\n"; + continue; + } + } + + stream << i.key() << "=" << i.value() << "\n"; + + } + + QStringList masters = mSettings.values(QString("master")); + for (int i = masters.count(); i--;) { + stream << "master=" << masters.at(i) << "\n"; + } + + QStringList plugins = mSettings.values(QString("plugin")); + for (int i = plugins.count(); i--;) { + stream << "plugin=" << plugins.at(i) << "\n"; + } + + return true; +} diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp new file mode 100644 index 000000000..6c296711f --- /dev/null +++ b/apps/launcher/settings/gamesettings.hpp @@ -0,0 +1,66 @@ +#ifndef GAMESETTINGS_HPP +#define GAMESETTINGS_HPP + +#include +#include +#include +#include + +#include + +namespace Files { typedef std::vector PathContainer; + struct ConfigurationManager;} + +class GameSettings +{ +public: + GameSettings(Files::ConfigurationManager &cfg); + ~GameSettings(); + + inline QString value(const QString &key, const QString &defaultValue = QString()) + { + return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + } + + + inline void setValue(const QString &key, const QString &value) + { + mSettings.insert(key, value); + } + + inline void setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + } + + inline void remove(const QString &key) + { + mSettings.remove(key); + } + + inline void clear() + { + mSettings.clear(); + } + + inline QStringList getDataDirs() { return mDataDirs; } + inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() {return mDataLocal; } + + QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); + bool readFile(QTextStream &stream); + bool writeFile(QTextStream &stream); + +private: + Files::ConfigurationManager &mCfgMgr; + + void validatePaths(); + QMap mSettings; + + QStringList mDataDirs; + QString mDataLocal; +}; + +#endif // GAMESETTINGS_HPP diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp new file mode 100644 index 000000000..0c5580091 --- /dev/null +++ b/apps/launcher/settings/graphicssettings.cpp @@ -0,0 +1,44 @@ +#include "graphicssettings.hpp" + +#include +#include +#include +#include + +GraphicsSettings::GraphicsSettings() +{ +} + +GraphicsSettings::~GraphicsSettings() +{ +} + +bool GraphicsSettings::writeFile(QTextStream &stream) +{ + QString sectionPrefix; + QRegExp sectionRe("([^/]+)/(.+)$"); + QMap settings = SettingsBase::getSettings(); + + QMapIterator i(settings); + while (i.hasNext()) { + i.next(); + + QString prefix; + QString key; + + if (sectionRe.exactMatch(i.key())) { + prefix = sectionRe.cap(1); + key = sectionRe.cap(2); + } + + if (sectionPrefix != prefix) { + sectionPrefix = prefix; + stream << "\n[" << prefix << "]\n"; + } + + stream << key << " = " << i.value() << "\n"; + } + + return true; + +} diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp new file mode 100644 index 000000000..3e8617849 --- /dev/null +++ b/apps/launcher/settings/graphicssettings.hpp @@ -0,0 +1,16 @@ +#ifndef GRAPHICSSETTINGS_HPP +#define GRAPHICSSETTINGS_HPP + +#include "settingsbase.hpp" + +class GraphicsSettings : public SettingsBase > +{ +public: + GraphicsSettings(); + ~GraphicsSettings(); + + bool writeFile(QTextStream &stream); + +}; + +#endif // GRAPHICSSETTINGS_HPP diff --git a/apps/launcher/settings/launchersettings.cpp b/apps/launcher/settings/launchersettings.cpp new file mode 100644 index 000000000..5d298e814 --- /dev/null +++ b/apps/launcher/settings/launchersettings.cpp @@ -0,0 +1,101 @@ +#include "launchersettings.hpp" + +#include +#include +#include +#include + +LauncherSettings::LauncherSettings() +{ +} + +LauncherSettings::~LauncherSettings() +{ +} + +QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) +{ + QMap settings = SettingsBase::getSettings(); + + if (flags == Qt::MatchExactly) + return settings.values(key); + + QStringList result; + + if (flags == Qt::MatchStartsWith) { + QStringList keys = settings.keys(); + + foreach (const QString ¤tKey, keys) { + if (currentKey.startsWith(key)) + result.append(settings.value(currentKey)); + } + } + + return result; +} + +QStringList LauncherSettings::subKeys(const QString &key) +{ + QMap settings = SettingsBase::getSettings(); + QStringList keys = settings.uniqueKeys(); + + QRegExp keyRe("(.+)/"); + + QStringList result; + + foreach (const QString ¤tKey, keys) { + + if (keyRe.indexIn(currentKey) != -1) { + + QString prefixedKey = keyRe.cap(1); + if(prefixedKey.startsWith(key)) { + + QString subKey = prefixedKey.remove(key); + if (!subKey.isEmpty()) + result.append(subKey); + } + } + } + + result.removeDuplicates(); + return result; +} + +bool LauncherSettings::writeFile(QTextStream &stream) +{ + QString sectionPrefix; + QRegExp sectionRe("([^/]+)/(.+)$"); + QMap settings = SettingsBase::getSettings(); + + QMapIterator i(settings); + i.toBack(); + + while (i.hasPrevious()) { + i.previous(); + + QString prefix; + QString key; + + if (sectionRe.exactMatch(i.key())) { + prefix = sectionRe.cap(1); + key = sectionRe.cap(2); + } + + // Get rid of legacy settings + if (key.contains(QChar('\\'))) + continue; + + if (key == QLatin1String("CurrentProfile")) + continue; + + if (sectionPrefix != prefix) { + sectionPrefix = prefix; + stream << "\n[" << prefix << "]\n"; + } + + stream << key << "=" << i.value() << "\n"; + } + + return true; + +} diff --git a/apps/launcher/settings/launchersettings.hpp b/apps/launcher/settings/launchersettings.hpp new file mode 100644 index 000000000..60c6f86bc --- /dev/null +++ b/apps/launcher/settings/launchersettings.hpp @@ -0,0 +1,19 @@ +#ifndef LAUNCHERSETTINGS_HPP +#define LAUNCHERSETTINGS_HPP + +#include "settingsbase.hpp" + +class LauncherSettings : public SettingsBase > +{ +public: + LauncherSettings(); + ~LauncherSettings(); + + QStringList subKeys(const QString &key); + QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); + + bool writeFile(QTextStream &stream); + +}; + +#endif // LAUNCHERSETTINGS_HPP diff --git a/apps/launcher/settings/settingsbase.hpp b/apps/launcher/settings/settingsbase.hpp new file mode 100644 index 000000000..bbfad1fbb --- /dev/null +++ b/apps/launcher/settings/settingsbase.hpp @@ -0,0 +1,98 @@ +#ifndef SETTINGSBASE_HPP +#define SETTINGSBASE_HPP + +#include +#include +#include +#include +#include + +#include + +template +class SettingsBase +{ + +public: + SettingsBase() {} + ~SettingsBase() {} + + inline QString value(const QString &key, const QString &defaultValue = QString()) + { + return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + } + + inline void setValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insert(key, value); + } + + inline void setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + } + + inline void remove(const QString &key) + { + mSettings.remove(key); + } + + Map getSettings() {return mSettings;} + + bool readFile(QTextStream &stream) + { + mCache.clear(); + + QString sectionPrefix; + QRegExp sectionRe("^\\[([^]]+)\\]"); + QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + + while (!stream.atEnd()) { + QString line = stream.readLine().simplified(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + if (sectionRe.exactMatch(line)) { + sectionPrefix = sectionRe.cap(1); + sectionPrefix.append("/"); + continue; + } + + if (keyRe.indexIn(line) != -1) { + + QString key = keyRe.cap(1).simplified(); + QString value = keyRe.cap(2).simplified(); + + if (!sectionPrefix.isEmpty()) + key.prepend(sectionPrefix); + + mSettings.remove(key); + + QStringList values = mCache.values(key); + if (!values.contains(value)) { + mCache.insertMulti(key, value); + } + } + } + + if (mSettings.isEmpty()) { + mSettings = mCache; // This is the first time we read a file + return true; + } + + // Merge the changed keys with those which didn't + mSettings.unite(mCache); + return true; + } + +private: + Map mSettings; + Map mCache; +}; + +#endif // SETTINGSBASE_HPP diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp new file mode 100644 index 000000000..41207a8de --- /dev/null +++ b/apps/launcher/utils/checkablemessagebox.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "checkablemessagebox.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/*! + \class Utils::CheckableMessageBox + + \brief A messagebox suitable for questions with a + "Do not ask me again" checkbox. + + Emulates the QMessageBox API with + static conveniences. The message label can open external URLs. +*/ + +class CheckableMessageBoxPrivate +{ +public: + CheckableMessageBoxPrivate(QDialog *q) + : clickedButton(0) + { + QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + + pixmapLabel = new QLabel(q); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); + pixmapLabel->setSizePolicy(sizePolicy); + pixmapLabel->setVisible(false); + + QSpacerItem *pixmapSpacer = + new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + + messageLabel = new QLabel(q); + messageLabel->setMinimumSize(QSize(300, 0)); + messageLabel->setWordWrap(true); + messageLabel->setOpenExternalLinks(true); + messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); + + QSpacerItem *checkBoxRightSpacer = + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); + QSpacerItem *buttonSpacer = + new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); + + checkBox = new QCheckBox(q); + checkBox->setText(CheckableMessageBox::tr("Do not ask again")); + + buttonBox = new QDialogButtonBox(q); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + + QVBoxLayout *verticalLayout = new QVBoxLayout(); + verticalLayout->addWidget(pixmapLabel); + verticalLayout->addItem(pixmapSpacer); + + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->addLayout(verticalLayout); + horizontalLayout_2->addWidget(messageLabel); + + QHBoxLayout *horizontalLayout = new QHBoxLayout(); + horizontalLayout->addWidget(checkBox); + horizontalLayout->addItem(checkBoxRightSpacer); + + QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); + verticalLayout_2->addLayout(horizontalLayout_2); + verticalLayout_2->addLayout(horizontalLayout); + verticalLayout_2->addItem(buttonSpacer); + verticalLayout_2->addWidget(buttonBox); + } + + QLabel *pixmapLabel; + QLabel *messageLabel; + QCheckBox *checkBox; + QDialogButtonBox *buttonBox; + QAbstractButton *clickedButton; +}; + +CheckableMessageBox::CheckableMessageBox(QWidget *parent) : + QDialog(parent), + d(new CheckableMessageBoxPrivate(this)) +{ + setModal(true); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + connect(d->buttonBox, SIGNAL(accepted()), SLOT(accept())); + connect(d->buttonBox, SIGNAL(rejected()), SLOT(reject())); + connect(d->buttonBox, SIGNAL(clicked(QAbstractButton*)), + SLOT(slotClicked(QAbstractButton*))); +} + +CheckableMessageBox::~CheckableMessageBox() +{ + delete d; +} + +void CheckableMessageBox::slotClicked(QAbstractButton *b) +{ + d->clickedButton = b; +} + +QAbstractButton *CheckableMessageBox::clickedButton() const +{ + return d->clickedButton; +} + +QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const +{ + if (d->clickedButton) + return d->buttonBox->standardButton(d->clickedButton); + return QDialogButtonBox::NoButton; +} + +QString CheckableMessageBox::text() const +{ + return d->messageLabel->text(); +} + +void CheckableMessageBox::setText(const QString &t) +{ + d->messageLabel->setText(t); +} + +QPixmap CheckableMessageBox::iconPixmap() const +{ + if (const QPixmap *p = d->pixmapLabel->pixmap()) + return QPixmap(*p); + return QPixmap(); +} + +void CheckableMessageBox::setIconPixmap(const QPixmap &p) +{ + d->pixmapLabel->setPixmap(p); + d->pixmapLabel->setVisible(!p.isNull()); +} + +bool CheckableMessageBox::isChecked() const +{ + return d->checkBox->isChecked(); +} + +void CheckableMessageBox::setChecked(bool s) +{ + d->checkBox->setChecked(s); +} + +QString CheckableMessageBox::checkBoxText() const +{ + return d->checkBox->text(); +} + +void CheckableMessageBox::setCheckBoxText(const QString &t) +{ + d->checkBox->setText(t); +} + +bool CheckableMessageBox::isCheckBoxVisible() const +{ + return d->checkBox->isVisible(); +} + +void CheckableMessageBox::setCheckBoxVisible(bool v) +{ + d->checkBox->setVisible(v); +} + +QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const +{ + return d->buttonBox->standardButtons(); +} + +void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) +{ + d->buttonBox->setStandardButtons(s); +} + +QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const +{ + return d->buttonBox->button(b); +} + +QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) +{ + return d->buttonBox->addButton(text, role); +} + +QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const +{ + foreach (QAbstractButton *b, d->buttonBox->buttons()) + if (QPushButton *pb = qobject_cast(b)) + if (pb->isDefault()) + return d->buttonBox->standardButton(pb); + return QDialogButtonBox::NoButton; +} + +void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) +{ + if (QPushButton *b = d->buttonBox->button(s)) { + b->setDefault(true); + b->setFocus(); + } +} + +QDialogButtonBox::StandardButton +CheckableMessageBox::question(QWidget *parent, + const QString &title, + const QString &question, + const QString &checkBoxText, + bool *checkBoxSetting, + QDialogButtonBox::StandardButtons buttons, + QDialogButtonBox::StandardButton defaultButton) +{ + CheckableMessageBox mb(parent); + mb.setWindowTitle(title); + mb.setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question)); + mb.setText(question); + mb.setCheckBoxText(checkBoxText); + mb.setChecked(*checkBoxSetting); + mb.setStandardButtons(buttons); + mb.setDefaultButton(defaultButton); + mb.exec(); + *checkBoxSetting = mb.isChecked(); + return mb.clickedStandardButton(); +} + +QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) +{ + return static_cast(int(db)); +} diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp new file mode 100644 index 000000000..93fd43fe1 --- /dev/null +++ b/apps/launcher/utils/checkablemessagebox.hpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CHECKABLEMESSAGEBOX_HPP +#define CHECKABLEMESSAGEBOX_HPP + +#include +#include +#include + +class CheckableMessageBoxPrivate; + +class CheckableMessageBox : public QDialog +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) + Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) + Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) + +public: + explicit CheckableMessageBox(QWidget *parent); + virtual ~CheckableMessageBox(); + + static QDialogButtonBox::StandardButton + question(QWidget *parent, + const QString &title, + const QString &question, + const QString &checkBoxText, + bool *checkBoxSetting, + QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, + QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); + + QString text() const; + void setText(const QString &); + + bool isChecked() const; + void setChecked(bool s); + + QString checkBoxText() const; + void setCheckBoxText(const QString &); + + bool isCheckBoxVisible() const; + void setCheckBoxVisible(bool); + + QDialogButtonBox::StandardButtons standardButtons() const; + void setStandardButtons(QDialogButtonBox::StandardButtons s); + QPushButton *button(QDialogButtonBox::StandardButton b) const; + QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); + + QDialogButtonBox::StandardButton defaultButton() const; + void setDefaultButton(QDialogButtonBox::StandardButton s); + + // See static QMessageBox::standardPixmap() + QPixmap iconPixmap() const; + void setIconPixmap (const QPixmap &p); + + // Query the result + QAbstractButton *clickedButton() const; + QDialogButtonBox::StandardButton clickedStandardButton() const; + + // Conversion convenience + static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); + +private slots: + void slotClicked(QAbstractButton *b); + +private: + CheckableMessageBoxPrivate *d; +}; + +#endif // CHECKABLEMESSAGEBOX_HPP diff --git a/apps/launcher/utils/filedialog.cpp b/apps/launcher/utils/filedialog.cpp deleted file mode 100644 index 16d677533..000000000 --- a/apps/launcher/utils/filedialog.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "filedialog.hpp" -#include -#include - -FileDialog::FileDialog(QWidget *parent) - : QFileDialog(parent) -{ - // Remove the default Choose button to prevent it being updated elsewhere - QDialogButtonBox *box = qFindChild(this); - Q_ASSERT(box); - box->removeButton(box->button(QDialogButtonBox::Open)); - - // Add our own button so we can disable/enable it - mChooseButton = new QPushButton(tr("&Choose")); - mChooseButton->setIcon(QIcon::fromTheme("document-open")); - mChooseButton->setEnabled(false); - box->addButton(mChooseButton, QDialogButtonBox::AcceptRole); - - connect(this, SIGNAL(directoryEntered(const QString&)), this, SLOT(updateChooseButton(const QString&))); - emit directoryEntered(QDir::currentPath()); -} - -QString FileDialog::getExistingDirectory(QWidget *parent, - const QString &caption, - const QString &dir, - Options options) -{ - // create a non-native file dialog - FileDialog dialog; - dialog.setFileMode(DirectoryOnly); - dialog.setOptions(options |= QFileDialog::DontUseNativeDialog | QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly); - - if (!caption.isEmpty()) - dialog.setWindowTitle(caption); - - if (!dir.isEmpty()) - dialog.setDirectory(dir); - - if (dialog.exec() == QDialog::Accepted) { - return dialog.selectedFiles().value(0); - } - return QString(); -} - -void FileDialog::updateChooseButton(const QString &directory) -{ - QDir currentDir = QDir(directory); - currentDir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); - currentDir.setNameFilters(QStringList() << "*.esm" << "*.esp"); - - if (!currentDir.entryList().isEmpty()) { - // There are data files in the current dir - mChooseButton->setEnabled(true); - } else { - mChooseButton->setEnabled(false); - } -} diff --git a/apps/launcher/utils/filedialog.hpp b/apps/launcher/utils/filedialog.hpp deleted file mode 100644 index 7a161ecb9..000000000 --- a/apps/launcher/utils/filedialog.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef FILEDIALOG_HPP -#define FILEDIALOG_HPP - -#include - -class QPushButton; - -class FileDialog : public QFileDialog -{ - Q_OBJECT - -public: - FileDialog(QWidget *parent = 0); - - static QString getExistingDirectory(QWidget *parent = 0, - const QString &caption = QString(), - const QString &dir = QString(), - Options options = ShowDirsOnly); - -private slots: - void updateChooseButton(const QString &directory); - -private: - QPushButton *mChooseButton; -}; - - -#endif // FILEDIALOG_HPP diff --git a/apps/launcher/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp deleted file mode 100644 index 8354d4a78..000000000 --- a/apps/launcher/utils/profilescombobox.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include - -#include "profilescombobox.hpp" - -ProfilesComboBox::ProfilesComboBox(QWidget *parent) : - QComboBox(parent) -{ - mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - - setEditable(true); - setValidator(mValidator); - setCompleter(0); - - connect(this, SIGNAL(currentIndexChanged(int)), this, - SLOT(slotIndexChanged(int))); - connect(lineEdit(), SIGNAL(returnPressed()), this, - SLOT(slotReturnPressed())); -} - -void ProfilesComboBox::setEditEnabled(bool editable) -{ - if (!editable) - return setEditable(false); - - // Reset the completer and validator - setEditable(true); - setValidator(mValidator); - setCompleter(0); -} - -void ProfilesComboBox::slotReturnPressed() -{ - QString current = currentText(); - QString previous = itemText(currentIndex()); - - if (findText(current) != -1) - return; - - setItemText(currentIndex(), current); - emit(profileRenamed(previous, current)); -} - -void ProfilesComboBox::slotIndexChanged(int index) -{ - if (index == -1) - return; - - emit(profileChanged(mOldProfile, currentText())); - mOldProfile = itemText(index); -} diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 16cadb661..011e51bf2 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -1,11 +1,11 @@ #include +#include #include -#include #include #include #include -#include "lineedit.hpp" +#include #include "textinputdialog.hpp" @@ -17,9 +17,19 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); - setMaximumHeight(height()); - setOkButtonEnabled(false); - setModal(true); + // Line edit + QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore + mLineEdit = new LineEdit(this); + mLineEdit->setValidator(validator); + mLineEdit->setCompleter(0); + + QLabel *label = new QLabel(this); + label->setText(text); + + QVBoxLayout *dialogLayout = new QVBoxLayout(this); + dialogLayout->addWidget(label); + dialogLayout->addWidget(mLineEdit); + dialogLayout->addWidget(mButtonBox); // Messageboxes on mac have no title #ifndef Q_OS_MAC @@ -28,22 +38,13 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid Q_UNUSED(title); #endif - QLabel *label = new QLabel(this); - label->setText(text); - - // Line edit - QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new LineEdit(this); - mLineEdit->setValidator(validator); - mLineEdit->setCompleter(0); - - QVBoxLayout *dialogLayout = new QVBoxLayout(this); - dialogLayout->addWidget(label); - dialogLayout->addWidget(mLineEdit); - dialogLayout->addWidget(mButtonBox); + setMaximumHeight(height()); + setOkButtonEnabled(false); + setModal(true); connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + } int TextInputDialog::exec() @@ -55,7 +56,17 @@ int TextInputDialog::exec() void TextInputDialog::setOkButtonEnabled(bool enabled) { - QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(enabled); + + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text,Qt::red); + + if (enabled) { + mLineEdit->setPalette(QApplication::palette()); + } else { + // Existing profile name, make the text red + mLineEdit->setPalette(*palette); + } + } diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 19b69794f..fc9ce417c 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -7,6 +7,8 @@ #include #include #include +#include + MwIniImporter::MwIniImporter() : mVerbose(false) @@ -18,8 +20,609 @@ MwIniImporter::MwIniImporter() { 0, 0 } }; const char *fallback[] = { + + // light + "LightAttenuation:UseConstant", + "LightAttenuation:ConstantValue", + "LightAttenuation:UseLinear", + "LightAttenuation:LinearMethod", + "LightAttenuation:LinearValue", + "LightAttenuation:LinearRadiusMult", + "LightAttenuation:UseQuadratic", + "LightAttenuation:QuadraticMethod", + "LightAttenuation:QuadraticValue", + "LightAttenuation:QuadraticRadiusMult", + "LightAttenuation:OutQuadInLin", + + // inventory + "Inventory:DirectionalDiffuseR", + "Inventory:DirectionalDiffuseG", + "Inventory:DirectionalDiffuseB", + "Inventory:DirectionalAmbientR", + "Inventory:DirectionalAmbientG", + "Inventory:DirectionalAmbientB", + "Inventory:DirectionalRotationX", + "Inventory:DirectionalRotationY", + "Inventory:UniformScaling", + + // map + "Map:Travel Siltstrider Red", + "Map:Travel Siltstrider Green", + "Map:Travel Siltstrider Blue", + "Map:Travel Boat Red", + "Map:Travel Boat Green", + "Map:Travel Boat Blue", + "Map:Travel Magic Red", + "Map:Travel Magic Green", + "Map:Travel Magic Blue", + "Map:Show Travel Lines", + + // water + "Water:Map Alpha", + "Water:World Alpha", + "Water:SurfaceTextureSize", + "Water:SurfaceTileCount", + "Water:SurfaceFPS", + "Water:SurfaceTexture", + "Water:SurfaceFrameCount", + "Water:TileTextureDivisor", + "Water:RippleTexture", + "Water:RippleFrameCount", + "Water:RippleLifetime", + "Water:MaxNumberRipples", + "Water:RippleScale", + "Water:RippleRotSpeed", + "Water:RippleAlphas", + "Water:PSWaterReflectTerrain", + "Water:PSWaterReflectUpdate", + "Water:NearWaterRadius", + "Water:NearWaterPoints", + "Water:NearWaterUnderwaterFreq", + "Water:NearWaterUnderwaterVolume", + "Water:NearWaterIndoorTolerance", + "Water:NearWaterOutdoorTolerance", + "Water:NearWaterIndoorID", + "Water:NearWaterOutdoorID", + "Water:UnderwaterSunriseFog", + "Water:UnderwaterDayFog", + "Water:UnderwaterSunsetFog", + "Water:UnderwaterNightFog", + "Water:UnderwaterIndoorFog", + "Water:UnderwaterColor", + "Water:UnderwaterColorWeight", + + // pixelwater + "PixelWater:SurfaceFPS", + "PixelWater:TileCount", + "PixelWater:Resolution", + + // fonts + "Fonts:Font 0", + "Fonts:Font 1", + "Fonts:Font 2", + + // UI colors + "FontColor:color_normal", + "FontColor:color_normal_over", + "FontColor:color_normal_pressed", + "FontColor:color_active", + "FontColor:color_active_over", + "FontColor:color_active_pressed", + "FontColor:color_disabled", + "FontColor:color_disabled_over", + "FontColor:color_disabled_pressed", + "FontColor:color_link", + "FontColor:color_link_over", + "FontColor:color_link_pressed", + "FontColor:color_journal_link", + "FontColor:color_journal_link_over", + "FontColor:color_journal_link_pressed", + "FontColor:color_journal_topic", + "FontColor:color_journal_topic_over", + "FontColor:color_journal_topic_pressed", + "FontColor:color_answer", + "FontColor:color_answer_over", + "FontColor:color_answer_pressed", + "FontColor:color_header", + "FontColor:color_notify", + "FontColor:color_big_normal", + "FontColor:color_big_normal_over", + "FontColor:color_big_normal_pressed", + "FontColor:color_big_link", + "FontColor:color_big_link_over", + "FontColor:color_big_link_pressed", + "FontColor:color_big_answer", + "FontColor:color_big_answer_over", + "FontColor:color_big_answer_pressed", + "FontColor:color_big_header", + "FontColor:color_big_notify", + "FontColor:color_background", + "FontColor:color_focus", + "FontColor:color_health", + "FontColor:color_magic", + "FontColor:color_fatigue", + "FontColor:color_misc", + "FontColor:color_weapon_fill", + "FontColor:color_magic_fill", + "FontColor:color_positive", + "FontColor:color_negative", + "FontColor:color_count", + + // level up messages + "Level Up:Level2", + "Level Up:Level3", + "Level Up:Level4", + "Level Up:Level5", + "Level Up:Level6", + "Level Up:Level7", + "Level Up:Level8", + "Level Up:Level9", + "Level Up:Level10", + "Level Up:Level11", + "Level Up:Level12", + "Level Up:Level13", + "Level Up:Level14", + "Level Up:Level15", + "Level Up:Level16", + "Level Up:Level17", + "Level Up:Level18", + "Level Up:Level19", + "Level Up:Level20", + "Level Up:Default", + + // character creation multiple choice test + "Question 1:Question", + "Question 1:AnswerOne", + "Question 1:AnswerTwo", + "Question 1:AnswerThree", + "Question 1:Sound", + "Question 2:Question", + "Question 2:AnswerOne", + "Question 2:AnswerTwo", + "Question 2:AnswerThree", + "Question 2:Sound", + "Question 3:Question", + "Question 3:AnswerOne", + "Question 3:AnswerTwo", + "Question 3:AnswerThree", + "Question 3:Sound", + "Question 4:Question", + "Question 4:AnswerOne", + "Question 4:AnswerTwo", + "Question 4:AnswerThree", + "Question 4:Sound", + "Question 5:Question", + "Question 5:AnswerOne", + "Question 5:AnswerTwo", + "Question 5:AnswerThree", + "Question 5:Sound", + "Question 6:Question", + "Question 6:AnswerOne", + "Question 6:AnswerTwo", + "Question 6:AnswerThree", + "Question 6:Sound", + "Question 7:Question", + "Question 7:AnswerOne", + "Question 7:AnswerTwo", + "Question 7:AnswerThree", + "Question 7:Sound", + "Question 8:Question", + "Question 8:AnswerOne", + "Question 8:AnswerTwo", + "Question 8:AnswerThree", + "Question 8:Sound", + "Question 9:Question", + "Question 9:AnswerOne", + "Question 9:AnswerTwo", + "Question 9:AnswerThree", + "Question 9:Sound", + "Question 10:Question", + "Question 10:AnswerOne", + "Question 10:AnswerTwo", + "Question 10:AnswerThree", + "Question 10:Sound", + + // blood textures and models + "Blood:Model 0", + "Blood:Model 1", + "Blood:Model 2", + "Blood:Texture 0", + "Blood:Texture 1", + "Blood:Texture 2", + "Blood:Texture Name 0", + "Blood:Texture Name 1", + "Blood:Texture Name 2", + + // movies + "Movies:Company Logo", + "Movies:Morrowind Logo", + "Movies:New Game", + "Movies:Loading", + "Movies:Options Menu", + + // weather related values + + "Weather Thunderstorm:Thunder Sound ID 0", + "Weather Thunderstorm:Thunder Sound ID 1", + "Weather Thunderstorm:Thunder Sound ID 2", + "Weather Thunderstorm:Thunder Sound ID 3", "Weather:Sunrise Time", "Weather:Sunset Time", + "Weather:Sunrise Duration", + "Weather:Sunset Duration", + "Weather:Hours Between Weather Changes", // AKA weather update time + "Weather Thunderstorm:Thunder Frequency", + "Weather Thunderstorm:Thunder Threshold", + + "Weather:EnvReduceColor", + "Weather:LerpCloseColor", + "Weather:BumpFadeColor", + "Weather:AlphaReduce", + "Weather:Minimum Time Between Environmental Sounds", + "Weather:Maximum Time Between Environmental Sounds", + "Weather:Sun Glare Fader Max", + "Weather:Sun Glare Fader Angle Max", + "Weather:Sun Glare Fader Color", + "Weather:Timescale Clouds", + "Weather:Precip Gravity", + "Weather:Rain Ripples", + "Weather:Rain Ripple Radius", + "Weather:Rain Ripples Per Drop", + "Weather:Rain Ripple Scale", + "Weather:Rain Ripple Speed", + "Weather:Fog Depth Change Speed", + "Weather:Sky Pre-Sunrise Time", + "Weather:Sky Post-Sunrise Time", + "Weather:Sky Pre-Sunset Time", + "Weather:Sky Post-Sunset Time", + "Weather:Ambient Pre-Sunrise Time", + "Weather:Ambient Post-Sunrise Time", + "Weather:Ambient Pre-Sunset Time", + "Weather:Ambient Post-Sunset Time", + "Weather:Fog Pre-Sunrise Time", + "Weather:Fog Post-Sunrise Time", + "Weather:Fog Pre-Sunset Time", + "Weather:Fog Post-Sunset Time", + "Weather:Sun Pre-Sunrise Time", + "Weather:Sun Post-Sunrise Time", + "Weather:Sun Pre-Sunset Time", + "Weather:Sun Post-Sunset Time", + "Weather:Stars Post-Sunset Start", + "Weather:Stars Pre-Sunrise Finish", + "Weather:Stars Fading Duration", + "Weather:Snow Ripples", + "Weather:Snow Ripple Radius", + "Weather:Snow Ripples Per Flake", + "Weather:Snow Ripple Scale", + "Weather:Snow Ripple Speed", + "Weather:Snow Gravity Scale", + "Weather:Snow High Kill", + "Weather:Snow Low Kill", + + "Weather Clear:Cloud Texture", + "Weather Clear:Clouds Maximum Percent", + "Weather Clear:Transition Delta", + "Weather Clear:Sky Sunrise Color", + "Weather Clear:Sky Day Color", + "Weather Clear:Sky Sunset Color", + "Weather Clear:Sky Night Color", + "Weather Clear:Fog Sunrise Color", + "Weather Clear:Fog Day Color", + "Weather Clear:Fog Sunset Color", + "Weather Clear:Fog Night Color", + "Weather Clear:Ambient Sunrise Color", + "Weather Clear:Ambient Day Color", + "Weather Clear:Ambient Sunset Color", + "Weather Clear:Ambient Night Color", + "Weather Clear:Sun Sunrise Color", + "Weather Clear:Sun Day Color", + "Weather Clear:Sun Sunset Color", + "Weather Clear:Sun Night Color", + "Weather Clear:Sun Disc Sunset Color", + "Weather Clear:Land Fog Day Depth", + "Weather Clear:Land Fog Night Depth", + "Weather Clear:Wind Speed", + "Weather Clear:Cloud Speed", + "Weather Clear:Glare View", + "Weather Clear:Ambient Loop Sound ID", + + "Weather Cloudy:Cloud Texture", + "Weather Cloudy:Clouds Maximum Percent", + "Weather Cloudy:Transition Delta", + "Weather Cloudy:Sky Sunrise Color", + "Weather Cloudy:Sky Day Color", + "Weather Cloudy:Sky Sunset Color", + "Weather Cloudy:Sky Night Color", + "Weather Cloudy:Fog Sunrise Color", + "Weather Cloudy:Fog Day Color", + "Weather Cloudy:Fog Sunset Color", + "Weather Cloudy:Fog Night Color", + "Weather Cloudy:Ambient Sunrise Color", + "Weather Cloudy:Ambient Day Color", + "Weather Cloudy:Ambient Sunset Color", + "Weather Cloudy:Ambient Night Color", + "Weather Cloudy:Sun Sunrise Color", + "Weather Cloudy:Sun Day Color", + "Weather Cloudy:Sun Sunset Color", + "Weather Cloudy:Sun Night Color", + "Weather Cloudy:Sun Disc Sunset Color", + "Weather Cloudy:Land Fog Day Depth", + "Weather Cloudy:Land Fog Night Depth", + "Weather Cloudy:Wind Speed", + "Weather Cloudy:Cloud Speed", + "Weather Cloudy:Glare View", + "Weather Cloudy:Ambient Loop Sound ID", + + "Weather Foggy:Cloud Texture", + "Weather Foggy:Clouds Maximum Percent", + "Weather Foggy:Transition Delta", + "Weather Foggy:Sky Sunrise Color", + "Weather Foggy:Sky Day Color", + "Weather Foggy:Sky Sunset Color", + "Weather Foggy:Sky Night Color", + "Weather Foggy:Fog Sunrise Color", + "Weather Foggy:Fog Day Color", + "Weather Foggy:Fog Sunset Color", + "Weather Foggy:Fog Night Color", + "Weather Foggy:Ambient Sunrise Color", + "Weather Foggy:Ambient Day Color", + "Weather Foggy:Ambient Sunset Color", + "Weather Foggy:Ambient Night Color", + "Weather Foggy:Sun Sunrise Color", + "Weather Foggy:Sun Day Color", + "Weather Foggy:Sun Sunset Color", + "Weather Foggy:Sun Night Color", + "Weather Foggy:Sun Disc Sunset Color", + "Weather Foggy:Land Fog Day Depth", + "Weather Foggy:Land Fog Night Depth", + "Weather Foggy:Wind Speed", + "Weather Foggy:Cloud Speed", + "Weather Foggy:Glare View", + "Weather Foggy:Ambient Loop Sound ID", + + "Weather Thunderstorm:Cloud Texture", + "Weather Thunderstorm:Clouds Maximum Percent", + "Weather Thunderstorm:Transition Delta", + "Weather Thunderstorm:Sky Sunrise Color", + "Weather Thunderstorm:Sky Day Color", + "Weather Thunderstorm:Sky Sunset Color", + "Weather Thunderstorm:Sky Night Color", + "Weather Thunderstorm:Fog Sunrise Color", + "Weather Thunderstorm:Fog Day Color", + "Weather Thunderstorm:Fog Sunset Color", + "Weather Thunderstorm:Fog Night Color", + "Weather Thunderstorm:Ambient Sunrise Color", + "Weather Thunderstorm:Ambient Day Color", + "Weather Thunderstorm:Ambient Sunset Color", + "Weather Thunderstorm:Ambient Night Color", + "Weather Thunderstorm:Sun Sunrise Color", + "Weather Thunderstorm:Sun Day Color", + "Weather Thunderstorm:Sun Sunset Color", + "Weather Thunderstorm:Sun Night Color", + "Weather Thunderstorm:Sun Disc Sunset Color", + "Weather Thunderstorm:Land Fog Day Depth", + "Weather Thunderstorm:Land Fog Night Depth", + "Weather Thunderstorm:Wind Speed", + "Weather Thunderstorm:Cloud Speed", + "Weather Thunderstorm:Glare View", + "Weather Thunderstorm:Rain Loop Sound ID", + "Weather Thunderstorm:Using Precip", + "Weather Thunderstorm:Rain Diameter", + "Weather Thunderstorm:Rain Height Min", + "Weather Thunderstorm:Rain Height Max", + "Weather Thunderstorm:Rain Threshold", + "Weather Thunderstorm:Max Raindrops", + "Weather Thunderstorm:Rain Entrance Speed", + "Weather Thunderstorm:Ambient Loop Sound ID", + "Weather Thunderstorm:Flash Decrement", + + "Weather Rain:Cloud Texture", + "Weather Rain:Clouds Maximum Percent", + "Weather Rain:Transition Delta", + "Weather Rain:Sky Sunrise Color", + "Weather Rain:Sky Day Color", + "Weather Rain:Sky Sunset Color", + "Weather Rain:Sky Night Color", + "Weather Rain:Fog Sunrise Color", + "Weather Rain:Fog Day Color", + "Weather Rain:Fog Sunset Color", + "Weather Rain:Fog Night Color", + "Weather Rain:Ambient Sunrise Color", + "Weather Rain:Ambient Day Color", + "Weather Rain:Ambient Sunset Color", + "Weather Rain:Ambient Night Color", + "Weather Rain:Sun Sunrise Color", + "Weather Rain:Sun Day Color", + "Weather Rain:Sun Sunset Color", + "Weather Rain:Sun Night Color", + "Weather Rain:Sun Disc Sunset Color", + "Weather Rain:Land Fog Day Depth", + "Weather Rain:Land Fog Night Depth", + "Weather Rain:Wind Speed", + "Weather Rain:Cloud Speed", + "Weather Rain:Glare View", + "Weather Rain:Rain Loop Sound ID", + "Weather Rain:Using Precip", + "Weather Rain:Rain Diameter", + "Weather Rain:Rain Height Min", + "Weather Rain:Rain Height Max", + "Weather Rain:Rain Threshold", + "Weather Rain:Rain Entrance Speed", + "Weather Rain:Ambient Loop Sound ID", + "Weather Rain:Max Raindrops", + + "Weather Overcast:Cloud Texture", + "Weather Overcast:Clouds Maximum Percent", + "Weather Overcast:Transition Delta", + "Weather Overcast:Sky Sunrise Color", + "Weather Overcast:Sky Day Color", + "Weather Overcast:Sky Sunset Color", + "Weather Overcast:Sky Night Color", + "Weather Overcast:Fog Sunrise Color", + "Weather Overcast:Fog Day Color", + "Weather Overcast:Fog Sunset Color", + "Weather Overcast:Fog Night Color", + "Weather Overcast:Ambient Sunrise Color", + "Weather Overcast:Ambient Day Color", + "Weather Overcast:Ambient Sunset Color", + "Weather Overcast:Ambient Night Color", + "Weather Overcast:Sun Sunrise Color", + "Weather Overcast:Sun Day Color", + "Weather Overcast:Sun Sunset Color", + "Weather Overcast:Sun Night Color", + "Weather Overcast:Sun Disc Sunset Color", + "Weather Overcast:Land Fog Day Depth", + "Weather Overcast:Land Fog Night Depth", + "Weather Overcast:Wind Speed", + "Weather Overcast:Cloud Speed", + "Weather Overcast:Glare View", + "Weather Overcast:Ambient Loop Sound ID", + + "Weather Ashstorm:Cloud Texture", + "Weather Ashstorm:Clouds Maximum Percent", + "Weather Ashstorm:Transition Delta", + "Weather Ashstorm:Sky Sunrise Color", + "Weather Ashstorm:Sky Day Color", + "Weather Ashstorm:Sky Sunset Color", + "Weather Ashstorm:Sky Night Color", + "Weather Ashstorm:Fog Sunrise Color", + "Weather Ashstorm:Fog Day Color", + "Weather Ashstorm:Fog Sunset Color", + "Weather Ashstorm:Fog Night Color", + "Weather Ashstorm:Ambient Sunrise Color", + "Weather Ashstorm:Ambient Day Color", + "Weather Ashstorm:Ambient Sunset Color", + "Weather Ashstorm:Ambient Night Color", + "Weather Ashstorm:Sun Sunrise Color", + "Weather Ashstorm:Sun Day Color", + "Weather Ashstorm:Sun Sunset Color", + "Weather Ashstorm:Sun Night Color", + "Weather Ashstorm:Sun Disc Sunset Color", + "Weather Ashstorm:Land Fog Day Depth", + "Weather Ashstorm:Land Fog Night Depth", + "Weather Ashstorm:Wind Speed", + "Weather Ashstorm:Cloud Speed", + "Weather Ashstorm:Glare View", + "Weather Ashstorm:Ambient Loop Sound ID", + "Weather Ashstorm:Storm Threshold", + + "Weather Blight:Cloud Texture", + "Weather Blight:Clouds Maximum Percent", + "Weather Blight:Transition Delta", + "Weather Blight:Sky Sunrise Color", + "Weather Blight:Sky Day Color", + "Weather Blight:Sky Sunset Color", + "Weather Blight:Sky Night Color", + "Weather Blight:Fog Sunrise Color", + "Weather Blight:Fog Day Color", + "Weather Blight:Fog Sunset Color", + "Weather Blight:Fog Night Color", + "Weather Blight:Ambient Sunrise Color", + "Weather Blight:Ambient Day Color", + "Weather Blight:Ambient Sunset Color", + "Weather Blight:Ambient Night Color", + "Weather Blight:Sun Sunrise Color", + "Weather Blight:Sun Day Color", + "Weather Blight:Sun Sunset Color", + "Weather Blight:Sun Night Color", + "Weather Blight:Sun Disc Sunset Color", + "Weather Blight:Land Fog Day Depth", + "Weather Blight:Land Fog Night Depth", + "Weather Blight:Wind Speed", + "Weather Blight:Cloud Speed", + "Weather Blight:Glare View", + "Weather Blight:Ambient Loop Sound ID", + "Weather Blight:Storm Threshold", + "Weather Blight:Disease Chance", + + // for Bloodmoon + "Weather Snow:Cloud Texture", + "Weather Snow:Clouds Maximum Percent", + "Weather Snow:Transition Delta", + "Weather Snow:Sky Sunrise Color", + "Weather Snow:Sky Day Color", + "Weather Snow:Sky Sunset Color", + "Weather Snow:Sky Night Color", + "Weather Snow:Fog Sunrise Color", + "Weather Snow:Fog Day Color", + "Weather Snow:Fog Sunset Color", + "Weather Snow:Fog Night Color", + "Weather Snow:Ambient Sunrise Color", + "Weather Snow:Ambient Day Color", + "Weather Snow:Ambient Sunset Color", + "Weather Snow:Ambient Night Color", + "Weather Snow:Sun Sunrise Color", + "Weather Snow:Sun Day Color", + "Weather Snow:Sun Sunset Color", + "Weather Snow:Sun Night Color", + "Weather Snow:Sun Disc Sunset Color", + "Weather Snow:Land Fog Day Depth", + "Weather Snow:Land Fog Night Depth", + "Weather Snow:Wind Speed", + "Weather Snow:Cloud Speed", + "Weather Snow:Glare View", + "Weather Snow:Snow Diameter", + "Weather Snow:Snow Height Min", + "Weather Snow:Snow Height Max", + "Weather Snow:Snow Entrance Speed", + "Weather Snow:Max Snowflakes", + "Weather Snow:Ambient Loop Sound ID", + "Weather Snow:Snow Threshold", + + // for Bloodmoon + "Weather Blizzard:Cloud Texture", + "Weather Blizzard:Clouds Maximum Percent", + "Weather Blizzard:Transition Delta", + "Weather Blizzard:Sky Sunrise Color", + "Weather Blizzard:Sky Day Color", + "Weather Blizzard:Sky Sunset Color", + "Weather Blizzard:Sky Night Color", + "Weather Blizzard:Fog Sunrise Color", + "Weather Blizzard:Fog Day Color", + "Weather Blizzard:Fog Sunset Color", + "Weather Blizzard:Fog Night Color", + "Weather Blizzard:Ambient Sunrise Color", + "Weather Blizzard:Ambient Day Color", + "Weather Blizzard:Ambient Sunset Color", + "Weather Blizzard:Ambient Night Color", + "Weather Blizzard:Sun Sunrise Color", + "Weather Blizzard:Sun Day Color", + "Weather Blizzard:Sun Sunset Color", + "Weather Blizzard:Sun Night Color", + "Weather Blizzard:Sun Disc Sunset Color", + "Weather Blizzard:Land Fog Day Depth", + "Weather Blizzard:Land Fog Night Depth", + "Weather Blizzard:Wind Speed", + "Weather Blizzard:Cloud Speed", + "Weather Blizzard:Glare View", + "Weather Blizzard:Ambient Loop Sound ID", + "Weather Blizzard:Storm Threshold", + + // moons + "Moons:Secunda Size", + "Moons:Secunda Axis Offset", + "Moons:Secunda Speed", + "Moons:Secunda Daily Increment", + "Moons:Secunda Moon Shadow Early Fade Angle", + "Moons:Secunda Fade Start Angle", + "Moons:Secunda Fade End Angle", + "Moons:Secunda Fade In Start", + "Moons:Secunda Fade In Finish", + "Moons:Secunda Fade Out Start", + "Moons:Secunda Fade Out Finish", + "Moons:Masser Size", + "Moons:Masser Axis Offset", + "Moons:Masser Speed", + "Moons:Masser Daily Increment", + "Moons:Masser Moon Shadow Early Fade Angle", + "Moons:Masser Fade Start Angle", + "Moons:Masser Fade End Angle", + "Moons:Masser Fade In Start", + "Moons:Masser Fade In Finish", + "Moons:Masser Fade Out Start", + "Moons:Masser Fade Out Finish", + "Moons:Script Color", + 0 }; @@ -48,14 +651,26 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::string section(""); MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); + ToUTF8::Utf8Encoder encoder(mEncoding); std::string line; while (std::getline(file, line)) { + line = encoder.getUtf8(line); + + // unify Unix-style and Windows file ending + if (!(line.empty()) && (line[line.length()-1]) == '\r') { + line = line.substr(0, line.length()-1); + } + if(line[0] == '[') { - if(line.length() > 2) { - section = line.substr(1, line.length()-3); + int pos = line.find(']'); + if(pos < 2) { + std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; + continue; } + + section = line.substr(1, line.find(']')-1); continue; } @@ -147,7 +762,7 @@ void MwIniImporter::mergeFallback(multistrmap &cfg, multistrmap &ini) { std::string value(*it); std::replace( value.begin(), value.end(), ' ', '_' ); std::replace( value.begin(), value.end(), ':', '_' ); - value.append(",").append(vc->substr(0,vc->length()-1)); + value.append(",").append(vc->substr(0,vc->length())); insertMultistrmap(cfg, "fallback", value); } } @@ -162,6 +777,39 @@ void MwIniImporter::insertMultistrmap(multistrmap &cfg, std::string key, std::st cfg[key].push_back(value); } +void MwIniImporter::importArchives(multistrmap &cfg, multistrmap &ini) { + std::vector archives; + std::string baseArchive("Archives:Archive "); + std::string archive; + + // Search archives listed in ini file + multistrmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + archive = baseArchive; + archive.append(this->numberToString(i)); + + it = ini.find(archive); + if(it == ini.end()) { + break; + } + + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { + archives.push_back(*entry); + } + } + + cfg.erase("fallback-archive"); + cfg.insert( std::make_pair > ("fallback-archive", std::vector())); + + // Add Morrowind.bsa by default, since Vanilla loads this archive even if it + // does not appears in the ini file + cfg["fallback-archive"].push_back("Morrowind.bsa"); + + for(std::vector::iterator it=archives.begin(); it!=archives.end(); ++it) { + cfg["fallback-archive"].push_back(*it); + } +} + void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { std::vector esmFiles; std::vector espFiles; @@ -179,8 +827,8 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { } for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { - std::string filetype(entry->substr(entry->length()-4, 3)); - std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + std::string filetype(entry->substr(entry->length()-3)); + Misc::StringUtils::toLower(filetype); if(filetype.compare("esm") == 0) { esmFiles.push_back(*entry); @@ -216,3 +864,8 @@ void MwIniImporter::writeToFile(boost::iostreams::stream #include +#include + class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; MwIniImporter(); + void setInputEncoding(const ToUTF8::FromType& encoding); void setVerbose(bool verbose); multistrmap loadIniFile(std::string filename); multistrmap loadCfgFile(std::string filename); void merge(multistrmap &cfg, multistrmap &ini); void mergeFallback(multistrmap &cfg, multistrmap &ini); void importGameFiles(multistrmap &cfg, multistrmap &ini); + void importArchives(multistrmap &cfg, multistrmap &ini); void writeToFile(boost::iostreams::stream &out, multistrmap &cfg); private: void insertMultistrmap(multistrmap &cfg, std::string key, std::string value); std::string numberToString(int n); + std::string toUTF8(const std::string &str); bool mVerbose; strmap mMergeMap; std::vector mMergeFallback; + ToUTF8::FromType mEncoding; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 234d7d57d..c9d88c0bb 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -18,6 +18,12 @@ int main(int argc, char *argv[]) { ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ("game-files,g", "import esm and esp files") + ("no-archives,A", "disable bsa archives import") + ("encoding,e", bpo::value()-> default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") ; p_desc.add("ini", 1).add("cfg", 1); @@ -57,6 +63,10 @@ int main(int argc, char *argv[]) { MwIniImporter importer; importer.setVerbose(vm.count("verbose")); + // Font encoding settings + std::string encoding(vm["encoding"].as()); + importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); @@ -67,6 +77,10 @@ int main(int argc, char *argv[]) { importer.importGameFiles(cfg, ini); } + if(!vm.count("no-archives")) { + importer.importArchives(cfg, ini); + } + std::cout << "write to: " << outputFile << std::endl; boost::iostreams::stream file(outputFile); importer.writeToFile(file, cfg); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt new file mode 100644 index 000000000..0071dd1fc --- /dev/null +++ b/apps/opencs/CMakeLists.txt @@ -0,0 +1,111 @@ + +set (OPENCS_SRC main.cpp) + +opencs_units (. editor) + + +opencs_units (model/doc + document + ) + +opencs_units_noqt (model/doc + documentmanager + ) + +opencs_hdrs_noqt (model/doc + state + ) + + +opencs_units (model/world + idtable idtableproxymodel + ) + + +opencs_units_noqt (model/world + universalid data record idcollection commands columnbase + ) + +opencs_hdrs_noqt (model/world + columns + ) + + +opencs_units (model/tools + tools operation reportmodel + ) + +opencs_units_noqt (model/tools + stage verifier mandatoryid + ) + + +opencs_units (view/doc + viewmanager view operations operation subview startup filedialog + ) + + +opencs_units_noqt (view/doc + subviewfactory + ) + +opencs_hdrs_noqt (view/doc + subviewfactoryimp + ) + + +opencs_units (view/world + table tablesubview + ) + +opencs_units_noqt (view/world + dialoguesubview util subviews enumdelegate vartypedelegate + ) + + +opencs_units (view/tools + reportsubview + ) + +opencs_units_noqt (view/tools + subviews + ) + + +set (OPENCS_US + ) + +set (OPENCS_RES ../../files/opencs/resources.qrc + ../../files/launcher/launcher.qrc + ) + +set (OPENCS_UI ../../files/ui/datafilespage.ui + ) + +source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) + +if(WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) + +find_package(Qt4 COMPONENTS QtCore QtGui QtXml QtXmlPatterns REQUIRED) +include(${QT_USE_FILE}) + +qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) +qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) +qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(opencs + ${OPENCS_SRC} + ${OPENCS_UI_HDR} + ${OPENCS_MOC_SRC} + ${OPENCS_RES_SRC} +) + +target_link_libraries(opencs + ${Boost_LIBRARIES} + ${QT_LIBRARIES} + components +) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp new file mode 100644 index 000000000..5966d089e --- /dev/null +++ b/apps/opencs/editor.cpp @@ -0,0 +1,117 @@ + +#include "editor.hpp" + +#include + +#include "model/doc/document.hpp" +#include "model/world/data.hpp" + +CS::Editor::Editor() : mViewManager (mDocumentManager) +{ + connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ())); + connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); + + connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ())); + connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); + + connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); + connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile())); + + setupDataFiles(); +} + +void CS::Editor::setupDataFiles() +{ + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) + ("data-local", boost::program_options::value()->default_value("")) + ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) + ("encoding", boost::program_options::value()->default_value("win1252")); + + boost::program_options::notify(variables); + + mCfgMgr.readConfiguration(variables, desc); + + Files::PathContainer mDataDirs, mDataLocal; + if (!variables["data"].empty()) { + mDataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + mDataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mCfgMgr.processPaths(mDataDirs); + mCfgMgr.processPaths(mDataLocal); + + // Set the charset for reading the esm/esp files + QString encoding = QString::fromStdString(variables["encoding"].as()); + mFileDialog.setEncoding(encoding); + + Files::PathContainer dataDirs; + dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end()); + dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end()); + + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + QString path = QString::fromStdString(iter->string()); + mFileDialog.addFiles(path); + } +} + +void CS::Editor::createDocument() +{ + mStartup.hide(); + + mFileDialog.newFile(); +} + +void CS::Editor::loadDocument() +{ + mStartup.hide(); + + mFileDialog.openFile(); +} + +void CS::Editor::openFiles() +{ + std::vector files; + QStringList paths = mFileDialog.checkedItemsPaths(); + + foreach (const QString &path, paths) { + files.push_back(path.toStdString()); + } + + CSMDoc::Document *document = mDocumentManager.addDocument(files, false); + + mViewManager.addView (document); + mFileDialog.hide(); +} + +void CS::Editor::createNewFile() +{ + std::vector files; + QStringList paths = mFileDialog.checkedItemsPaths(); + + foreach (const QString &path, paths) { + files.push_back(path.toStdString()); + } + + files.push_back(mFileDialog.fileName().toStdString()); + + CSMDoc::Document *document = mDocumentManager.addDocument (files, true); + + mViewManager.addView (document); + mFileDialog.hide(); +} + +int CS::Editor::run() +{ + mStartup.show(); + + return QApplication::exec(); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp new file mode 100644 index 000000000..c242a17ea --- /dev/null +++ b/apps/opencs/editor.hpp @@ -0,0 +1,50 @@ +#ifndef CS_EDITOR_H +#define CS_EDITOR_H + +#include + +#include + +#include "model/doc/documentmanager.hpp" + +#include "view/doc/viewmanager.hpp" +#include "view/doc/startup.hpp" +#include "view/doc/filedialog.hpp" + +namespace CS +{ + class Editor : public QObject + { + Q_OBJECT + + CSMDoc::DocumentManager mDocumentManager; + CSVDoc::ViewManager mViewManager; + CSVDoc::StartupDialogue mStartup; + FileDialog mFileDialog; + + Files::ConfigurationManager mCfgMgr; + + void setupDataFiles(); + + // not implemented + Editor (const Editor&); + Editor& operator= (const Editor&); + + public: + + Editor(); + + int run(); + ///< \return error status + + private slots: + + void createDocument(); + + void loadDocument(); + void openFiles(); + void createNewFile(); + }; +} + +#endif diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp new file mode 100644 index 000000000..aa315804b --- /dev/null +++ b/apps/opencs/main.cpp @@ -0,0 +1,43 @@ + +#include "editor.hpp" + +#include +#include + +#include +#include + +class Application : public QApplication +{ + private: + + bool notify (QObject *receiver, QEvent *event) + { + try + { + return QApplication::notify (receiver, event); + } + catch (const std::exception& exception) + { + std::cerr << "An exception has been caught: " << exception.what() << std::endl; + } + + return false; + } + + public: + + Application (int& argc, char *argv[]) : QApplication (argc, argv) {} +}; + +int main(int argc, char *argv[]) +{ + Q_INIT_RESOURCE (resources); + Application mApplication (argc, argv); + + mApplication.setWindowIcon (QIcon (":./opencs.png")); + + CS::Editor editor; + + return editor.run(); +} diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp new file mode 100644 index 000000000..11d877d0b --- /dev/null +++ b/apps/opencs/model/doc/document.cpp @@ -0,0 +1,336 @@ + +#include "document.hpp" + +#include + +void CSMDoc::Document::load (const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, bool lastAsModified) +{ + assert (begin!=end); + + std::vector::const_iterator end2 (end); + + if (lastAsModified) + --end2; + + for (std::vector::const_iterator iter (begin); iter!=end2; ++iter) + getData().loadFile (*iter, true); + + if (lastAsModified) + getData().loadFile (*end2, false); +} + +void CSMDoc::Document::addOptionalGmsts() +{ + static const char *sFloats[] = + { + "fCombatDistanceWerewolfMod", + "fFleeDistance", + "fWereWolfAcrobatics", + "fWereWolfAgility", + "fWereWolfAlchemy", + "fWereWolfAlteration", + "fWereWolfArmorer", + "fWereWolfAthletics", + "fWereWolfAxe", + "fWereWolfBlock", + "fWereWolfBluntWeapon", + "fWereWolfConjuration", + "fWereWolfDestruction", + "fWereWolfEnchant", + "fWereWolfEndurance", + "fWereWolfFatigue", + "fWereWolfHandtoHand", + "fWereWolfHealth", + "fWereWolfHeavyArmor", + "fWereWolfIllusion", + "fWereWolfIntellegence", + "fWereWolfLightArmor", + "fWereWolfLongBlade", + "fWereWolfLuck", + "fWereWolfMagicka", + "fWereWolfMarksman", + "fWereWolfMediumArmor", + "fWereWolfMerchantile", + "fWereWolfMysticism", + "fWereWolfPersonality", + "fWereWolfRestoration", + "fWereWolfRunMult", + "fWereWolfSecurity", + "fWereWolfShortBlade", + "fWereWolfSilverWeaponDamageMult", + "fWereWolfSneak", + "fWereWolfSpear", + "fWereWolfSpeechcraft", + "fWereWolfSpeed", + "fWereWolfStrength", + "fWereWolfUnarmored", + "fWereWolfWillPower", + 0 + }; + + static const char *sIntegers[] = + { + "iWereWolfBounty", + "iWereWolfFightMod", + "iWereWolfFleeMod", + "iWereWolfLevelToAttack", + 0 + }; + + static const char *sStrings[] = + { + "sCompanionShare", + "sCompanionWarningButtonOne", + "sCompanionWarningButtonTwo", + "sCompanionWarningMessage", + "sDeleteNote", + "sEditNote", + "sEffectSummonCreature01", + "sEffectSummonCreature02", + "sEffectSummonCreature03", + "sEffectSummonCreature04", + "sEffectSummonCreature05", + "sEffectSummonFabricant", + "sLevitateDisabled", + "sMagicCreature01ID", + "sMagicCreature02ID", + "sMagicCreature03ID", + "sMagicCreature04ID", + "sMagicCreature05ID", + "sMagicFabricantID", + "sMaxSale", + "sProfitValue", + "sTeleportDisabled", + "sWerewolfAlarmMessage", + "sWerewolfPopup", + "sWerewolfRefusal", + "sWerewolfRestMessage", + 0 + }; + + for (int i=0; sFloats[i]; ++i) + { + ESM::GameSetting gmst; + gmst.mId = sFloats[i]; + gmst.mValue.setType (ESM::VT_Float); + addOptionalGmst (gmst); + } + + for (int i=0; sIntegers[i]; ++i) + { + ESM::GameSetting gmst; + gmst.mId = sIntegers[i]; + gmst.mValue.setType (ESM::VT_Int); + addOptionalGmst (gmst); + } + + for (int i=0; sStrings[i]; ++i) + { + ESM::GameSetting gmst; + gmst.mId = sStrings[i]; + gmst.mValue.setType (ESM::VT_String); + gmst.mValue.setString (""); + addOptionalGmst (gmst); + } +} + +void CSMDoc::Document::addOptionalGlobals() +{ + static const char *sGlobals[] = + { + "dayspassed", + "pcwerewolf", + "pcyear", + 0 + }; + + for (int i=0; sGlobals[i]; ++i) + { + ESM::Global global; + global.mId = sGlobals[i]; + global.mValue.setType (ESM::VT_Long); + addOptionalGlobal (global); + } +} + +void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst) +{ + if (getData().getGmsts().searchId (gmst.mId)==-1) + { + CSMWorld::Record record; + record.mBase = gmst; + record.mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGmsts().appendRecord (record); + } +} + +void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global) +{ + if (getData().getGlobals().searchId (global.mId)==-1) + { + CSMWorld::Record record; + record.mBase = global; + record.mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGlobals().appendRecord (record); + } +} + +void CSMDoc::Document::createBase() +{ + static const char *sGlobals[] = + { + "Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0 + }; + + for (int i=0; sGlobals[i]; ++i) + { + ESM::Global record; + + record.mId = sGlobals[i]; + + record.mValue.setType (i==2 ? ESM::VT_Float : ESM::VT_Int); + + if (i==0) + record.mValue.setInteger (1); + + getData().getGlobals().add (record); + } + + /// \todo add GMSTs +} + +CSMDoc::Document::Document (const std::vector& files, bool new_) +: mTools (mData) +{ + if (files.empty()) + throw std::runtime_error ("Empty content file sequence"); + + /// \todo adjust last file name: + /// \li make sure it is located in the data-local directory (adjust path if necessary) + /// \li make sure the extension matches the new scheme (change it if necesarry) + + mName = files.back().filename().string(); + + if (new_ && files.size()==1) + createBase(); + else if (files.size()>1) + { + std::vector::const_iterator end = files.end(); + + if (new_) + --end; + + load (files.begin(), end, !new_); + } + + addOptionalGmsts(); + addOptionalGlobals(); + + connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); + + connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); + connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int))); + + // dummy implementation -> remove when proper save is implemented. + mSaveCount = 0; + connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving())); +} + +QUndoStack& CSMDoc::Document::getUndoStack() +{ + return mUndoStack; +} + +int CSMDoc::Document::getState() const +{ + int state = 0; + + if (!mUndoStack.isClean()) + state |= State_Modified; + + if (mSaveCount) + state |= State_Locked | State_Saving | State_Operation; + + if (int operations = mTools.getRunningOperations()) + state |= State_Locked | State_Operation | operations; + + return state; +} + +const std::string& CSMDoc::Document::getName() const +{ + return mName; +} + +void CSMDoc::Document::save() +{ + mSaveCount = 1; + mSaveTimer.start (500); + emit stateChanged (getState(), this); + emit progress (1, 16, State_Saving, 1, this); +} + +CSMWorld::UniversalId CSMDoc::Document::verify() +{ + CSMWorld::UniversalId id = mTools.runVerifier(); + emit stateChanged (getState(), this); + return id; +} + +void CSMDoc::Document::abortOperation (int type) +{ + mTools.abortOperation (type); + + if (type==State_Saving) + { + mSaveCount=0; + mSaveTimer.stop(); + emit stateChanged (getState(), this); + } +} + +void CSMDoc::Document::modificationStateChanged (bool clean) +{ + emit stateChanged (getState(), this); +} + +void CSMDoc::Document::operationDone (int type) +{ + emit stateChanged (getState(), this); +} + +void CSMDoc::Document::saving() +{ + ++mSaveCount; + + emit progress (mSaveCount, 16, State_Saving, 1, this); + + if (mSaveCount>15) + { + mSaveCount = 0; + mSaveTimer.stop(); + mUndoStack.setClean(); + emit stateChanged (getState(), this); + } +} + +const CSMWorld::Data& CSMDoc::Document::getData() const +{ + return mData; +} + +CSMWorld::Data& CSMDoc::Document::getData() +{ + return mData; +} + +CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& id) +{ + return mTools.getReport (id); +} + +void CSMDoc::Document::progress (int current, int max, int type) +{ + emit progress (current, max, type, 1, this); +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp new file mode 100644 index 000000000..a7b198689 --- /dev/null +++ b/apps/opencs/model/doc/document.hpp @@ -0,0 +1,108 @@ +#ifndef CSM_DOC_DOCUMENT_H +#define CSM_DOC_DOCUMENT_H + +#include + +#include + +#include +#include +#include + +#include "../world/data.hpp" + +#include "../tools/tools.hpp" + +#include "state.hpp" + +class QAbstractItemModel; + +namespace ESM +{ + struct GameSetting; + struct Global; +} + +namespace CSMDoc +{ + class Document : public QObject + { + Q_OBJECT + + private: + + std::string mName; ///< \todo replace name with ESX list + CSMWorld::Data mData; + CSMTools::Tools mTools; + + // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is + // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. + QUndoStack mUndoStack; + + int mSaveCount; ///< dummy implementation -> remove when proper save is implemented. + QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented. + + // not implemented + Document (const Document&); + Document& operator= (const Document&); + + void load (const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, bool lastAsModified); + ///< \param lastAsModified Store the last file in Modified instead of merging it into Base. + + void createBase(); + + void addOptionalGmsts(); + + void addOptionalGlobals(); + + void addOptionalGmst (const ESM::GameSetting& gmst); + + void addOptionalGlobal (const ESM::Global& global); + + public: + + Document (const std::vector& files, bool new_); + + QUndoStack& getUndoStack(); + + int getState() const; + + const std::string& getName() const; + ///< \todo replace with ESX list + + void save(); + + CSMWorld::UniversalId verify(); + + void abortOperation (int type); + + const CSMWorld::Data& getData() const; + + CSMWorld::Data& getData(); + + CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. + + signals: + + void stateChanged (int state, CSMDoc::Document *document); + + void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + + private slots: + + void modificationStateChanged (bool clean); + + void operationDone (int type); + + void saving(); + ///< dummy implementation -> remove when proper save is implemented. + + public slots: + + void progress (int current, int max, int type); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp new file mode 100644 index 000000000..740c0b582 --- /dev/null +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -0,0 +1,38 @@ + +#include "documentmanager.hpp" + +#include +#include + +#include "document.hpp" + +CSMDoc::DocumentManager::DocumentManager() {} + +CSMDoc::DocumentManager::~DocumentManager() +{ + for (std::vector::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) + delete *iter; +} + +CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, + bool new_) +{ + Document *document = new Document (files, new_); + + mDocuments.push_back (document); + + return document; +} + +bool CSMDoc::DocumentManager::removeDocument (Document *document) +{ + std::vector::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document); + + if (iter==mDocuments.end()) + throw std::runtime_error ("removing invalid document"); + + mDocuments.erase (iter); + delete document; + + return mDocuments.empty(); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp new file mode 100644 index 000000000..a307b76a5 --- /dev/null +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -0,0 +1,37 @@ +#ifndef CSM_DOC_DOCUMENTMGR_H +#define CSM_DOC_DOCUMENTMGR_H + +#include +#include + +#include + +namespace CSMDoc +{ + class Document; + + class DocumentManager + { + std::vector mDocuments; + + DocumentManager (const DocumentManager&); + DocumentManager& operator= (const DocumentManager&); + + public: + + DocumentManager(); + + ~DocumentManager(); + + Document *addDocument (const std::vector& files, bool new_); + ///< The ownership of the returned document is not transferred to the caller. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + + bool removeDocument (Document *document); + ///< \return last document removed? + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp new file mode 100644 index 000000000..04e6fae89 --- /dev/null +++ b/apps/opencs/model/doc/state.hpp @@ -0,0 +1,19 @@ +#ifndef CSM_DOC_STATE_H +#define CSM_DOC_STATE_H + +namespace CSMDoc +{ + enum State + { + State_Modified = 1, + State_Locked = 2, + State_Operation = 4, + + State_Saving = 8, + State_Verifying = 16, + State_Compiling = 32, // not implemented yet + State_Searching = 64 // not implemented yet + }; +} + +#endif diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp new file mode 100644 index 000000000..f9f2ca378 --- /dev/null +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -0,0 +1,21 @@ + +#include "mandatoryid.hpp" + +#include "../world/idcollection.hpp" + +CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::IdCollectionBase& idCollection, + const CSMWorld::UniversalId& collectionId, const std::vector& ids) +: mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids) +{} + +int CSMTools::MandatoryIdStage::setup() +{ + return mIds.size(); +} + +void CSMTools::MandatoryIdStage::perform (int stage, std::vector& messages) +{ + if (mIdCollection.searchId (mIds.at (stage))==-1 || + mIdCollection.getRecord (mIds.at (stage)).isDeleted()) + messages.push_back (mCollectionId.toString() + "|Missing mandatory record: " + mIds.at (stage)); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp new file mode 100644 index 000000000..14fcec204 --- /dev/null +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -0,0 +1,38 @@ +#ifndef CSM_TOOLS_MANDATORYID_H +#define CSM_TOOLS_MANDATORYID_H + +#include +#include + +#include "../world/universalid.hpp" + +#include "stage.hpp" + +namespace CSMWorld +{ + class IdCollectionBase; +} + +namespace CSMTools +{ + /// \brief Verify stage: make sure that records with specific IDs exist. + class MandatoryIdStage : public Stage + { + const CSMWorld::IdCollectionBase& mIdCollection; + CSMWorld::UniversalId mCollectionId; + std::vector mIds; + + public: + + MandatoryIdStage (const CSMWorld::IdCollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, + const std::vector& ids); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/operation.cpp b/apps/opencs/model/tools/operation.cpp new file mode 100644 index 000000000..71761cdae --- /dev/null +++ b/apps/opencs/model/tools/operation.cpp @@ -0,0 +1,84 @@ + +#include "operation.hpp" + +#include +#include + +#include + +#include "../doc/state.hpp" + +#include "stage.hpp" + +void CSMTools::Operation::prepareStages() +{ + mCurrentStage = mStages.begin(); + mCurrentStep = 0; + mCurrentStepTotal = 0; + mTotalSteps = 0; + + for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + { + iter->second = iter->first->setup(); + mTotalSteps += iter->second; + } +} + +CSMTools::Operation::Operation (int type) : mType (type) {} + +CSMTools::Operation::~Operation() +{ + for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + delete iter->first; +} + +void CSMTools::Operation::run() +{ + prepareStages(); + + QTimer timer; + + timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify())); + + timer.start (0); + + exec(); +} + +void CSMTools::Operation::appendStage (Stage *stage) +{ + mStages.push_back (std::make_pair (stage, 0)); +} + +void CSMTools::Operation::abort() +{ + exit(); +} + +void CSMTools::Operation::verify() +{ + std::vector messages; + + while (mCurrentStage!=mStages.end()) + { + if (mCurrentStep>=mCurrentStage->second) + { + mCurrentStep = 0; + ++mCurrentStage; + } + else + { + mCurrentStage->first->perform (mCurrentStep++, messages); + ++mCurrentStepTotal; + break; + } + } + + emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); + + for (std::vector::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) + emit reportMessage (iter->c_str(), mType); + + if (mCurrentStage==mStages.end()) + exit(); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/operation.hpp b/apps/opencs/model/tools/operation.hpp new file mode 100644 index 000000000..4731c58fa --- /dev/null +++ b/apps/opencs/model/tools/operation.hpp @@ -0,0 +1,54 @@ +#ifndef CSM_TOOLS_OPERATION_H +#define CSM_TOOLS_OPERATION_H + +#include + +#include + +namespace CSMTools +{ + class Stage; + + class Operation : public QThread + { + Q_OBJECT + + int mType; + std::vector > mStages; // stage, number of steps + std::vector >::iterator mCurrentStage; + int mCurrentStep; + int mCurrentStepTotal; + int mTotalSteps; + + void prepareStages(); + + public: + + Operation (int type); + + virtual ~Operation(); + + virtual void run(); + + void appendStage (Stage *stage); + ///< The ownership of \a stage is transferred to *this. + /// + /// \attention Do no call this function while this Operation is running. + + signals: + + void progress (int current, int max, int type); + + void reportMessage (const QString& message, int type); + + public slots: + + void abort(); + + private slots: + + void verify(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp new file mode 100644 index 000000000..b12531875 --- /dev/null +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -0,0 +1,71 @@ + +#include "reportmodel.hpp" + +#include + +int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mRows.size(); +} + +int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return 2; +} + +QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const +{ + if (role!=Qt::DisplayRole) + return QVariant(); + + if (index.column()==0) + return static_cast (mRows.at (index.row()).first.getType()); + else + return mRows.at (index.row()).second.c_str(); +} + +QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (role!=Qt::DisplayRole) + return QVariant(); + + if (orientation==Qt::Vertical) + return QVariant(); + + return tr (section==0 ? "Type" : "Description"); +} + +bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) +{ + if (parent.isValid()) + return false; + + mRows.erase (mRows.begin()+row, mRows.begin()+row+count); + + return true; +} + +void CSMTools::ReportModel::add (const std::string& row) +{ + std::string::size_type index = row.find ('|'); + + if (index==std::string::npos) + throw std::logic_error ("invalid report message"); + + beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); + + mRows.push_back (std::make_pair (row.substr (0, index), row.substr (index+1))); + + endInsertRows(); +} + +const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const +{ + return mRows.at (row).first; +} \ No newline at end of file diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp new file mode 100644 index 000000000..55c25d907 --- /dev/null +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -0,0 +1,37 @@ +#ifndef CSM_TOOLS_REPORTMODEL_H +#define CSM_TOOLS_REPORTMODEL_H + +#include +#include + +#include + +#include "../world/universalid.hpp" + +namespace CSMTools +{ + class ReportModel : public QAbstractTableModel + { + Q_OBJECT + + std::vector > mRows; + + public: + + virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; + + virtual int columnCount (const QModelIndex & parent = QModelIndex()) const; + + virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); + + void add (const std::string& row); + + const CSMWorld::UniversalId& getUniversalId (int row) const; + }; +} + +#endif diff --git a/apps/opencs/model/tools/stage.cpp b/apps/opencs/model/tools/stage.cpp new file mode 100644 index 000000000..6f4567e57 --- /dev/null +++ b/apps/opencs/model/tools/stage.cpp @@ -0,0 +1,4 @@ + +#include "stage.hpp" + +CSMTools::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/stage.hpp b/apps/opencs/model/tools/stage.hpp new file mode 100644 index 000000000..3020936f3 --- /dev/null +++ b/apps/opencs/model/tools/stage.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_TOOLS_STAGE_H +#define CSM_TOOLS_STAGE_H + +#include +#include + +namespace CSMTools +{ + class Stage + { + public: + + virtual ~Stage(); + + virtual int setup() = 0; + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages) = 0; + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif + diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp new file mode 100644 index 000000000..8dd1c0fe6 --- /dev/null +++ b/apps/opencs/model/tools/tools.cpp @@ -0,0 +1,123 @@ + +#include "tools.hpp" + +#include + +#include "verifier.hpp" + +#include "../doc/state.hpp" + +#include "../world/data.hpp" +#include "../world/universalid.hpp" + +#include "reportmodel.hpp" +#include "mandatoryid.hpp" + +CSMTools::Operation *CSMTools::Tools::get (int type) +{ + switch (type) + { + case CSMDoc::State_Verifying: return mVerifier; + } + + return 0; +} + +const CSMTools::Operation *CSMTools::Tools::get (int type) const +{ + return const_cast (this)->get (type); +} + +CSMTools::Verifier *CSMTools::Tools::getVerifier() +{ + if (!mVerifier) + { + mVerifier = new Verifier; + + connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); + connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), + this, SLOT (verifierMessage (const QString&, int))); + + std::vector mandatoryIds; // I want C++11, damn it! + mandatoryIds.push_back ("Day"); + mandatoryIds.push_back ("DaysPassed"); + mandatoryIds.push_back ("GameHour"); + mandatoryIds.push_back ("Month"); + mandatoryIds.push_back ("PCRace"); + mandatoryIds.push_back ("PCVampire"); + mandatoryIds.push_back ("PCWerewolf"); + mandatoryIds.push_back ("PCYear"); + + mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(), + CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); + } + + return mVerifier; +} + +CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0) +{ + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) + delete iter->second; +} + +CSMTools::Tools::~Tools() +{ + delete mVerifier; +} + +CSMWorld::UniversalId CSMTools::Tools::runVerifier() +{ + mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); + mActiveReports[CSMDoc::State_Verifying] = mNextReportNumber-1; + + getVerifier()->start(); + + return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1); +} + +void CSMTools::Tools::abortOperation (int type) +{ + if (Operation *operation = get (type)) + operation->abort(); +} + +int CSMTools::Tools::getRunningOperations() const +{ + static const int sOperations[] = + { + CSMDoc::State_Verifying, + -1 + }; + + int result = 0; + + for (int i=0; sOperations[i]!=-1; ++i) + if (const Operation *operation = get (sOperations[i])) + if (operation->isRunning()) + result |= sOperations[i]; + + return result; +} + +CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) +{ + if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults) + throw std::logic_error ("invalid request for report model: " + id.toString()); + + return mReports.at (id.getIndex()); +} + +void CSMTools::Tools::verifierDone() +{ + emit done (CSMDoc::State_Verifying); +} + +void CSMTools::Tools::verifierMessage (const QString& message, int type) +{ + std::map::iterator iter = mActiveReports.find (type); + + if (iter!=mActiveReports.end()) + mReports[iter->second]->add (message.toStdString()); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp new file mode 100644 index 000000000..652345c6d --- /dev/null +++ b/apps/opencs/model/tools/tools.hpp @@ -0,0 +1,73 @@ +#ifndef CSM_TOOLS_TOOLS_H +#define CSM_TOOLS_TOOLS_H + +#include + +#include + +namespace CSMWorld +{ + class Data; + class UniversalId; +} + +namespace CSMTools +{ + class Verifier; + class Operation; + class ReportModel; + + class Tools : public QObject + { + Q_OBJECT + + CSMWorld::Data& mData; + Verifier *mVerifier; + std::map mReports; + int mNextReportNumber; + std::map mActiveReports; // type, report number + + // not implemented + Tools (const Tools&); + Tools& operator= (const Tools&); + + Verifier *getVerifier(); + + Operation *get (int type); + ///< Returns a 0-pointer, if operation hasn't been used yet. + + const Operation *get (int type) const; + ///< Returns a 0-pointer, if operation hasn't been used yet. + + public: + + Tools (CSMWorld::Data& data); + + virtual ~Tools(); + + CSMWorld::UniversalId runVerifier(); + ///< \return ID of the report for this verification run + + void abortOperation (int type); + ///< \attention The operation is not aborted immediately. + + int getRunningOperations() const; + + ReportModel *getReport (const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. + + private slots: + + void verifierDone(); + + void verifierMessage (const QString& message, int type); + + signals: + + void progress (int current, int max, int type); + + void done (int type); + }; +} + +#endif diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp new file mode 100644 index 000000000..9c00d4ea7 --- /dev/null +++ b/apps/opencs/model/tools/verifier.cpp @@ -0,0 +1,7 @@ + +#include "verifier.hpp" + +#include "../doc/state.hpp" + +CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying) +{} diff --git a/apps/opencs/model/tools/verifier.hpp b/apps/opencs/model/tools/verifier.hpp new file mode 100644 index 000000000..054f87169 --- /dev/null +++ b/apps/opencs/model/tools/verifier.hpp @@ -0,0 +1,17 @@ +#ifndef CSM_TOOLS_VERIFIER_H +#define CSM_TOOLS_VERIFIER_H + +#include "operation.hpp" + +namespace CSMTools +{ + class Verifier : public Operation + { + public: + + Verifier(); + + }; +} + +#endif diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp new file mode 100644 index 000000000..134c582c4 --- /dev/null +++ b/apps/opencs/model/world/columnbase.cpp @@ -0,0 +1,13 @@ + +#include "columnbase.hpp" + +CSMWorld::ColumnBase::ColumnBase (const std::string& title, Display displayType, int flags) +: mTitle (title), mDisplayType (displayType), mFlags (flags) +{} + +CSMWorld::ColumnBase::~ColumnBase() {} + +bool CSMWorld::ColumnBase::isUserEditable() const +{ + return isEditable(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp new file mode 100644 index 000000000..c44abda2b --- /dev/null +++ b/apps/opencs/model/world/columnbase.hpp @@ -0,0 +1,70 @@ +#ifndef CSM_WOLRD_COLUMNBASE_H +#define CSM_WOLRD_COLUMNBASE_H + +#include + +#include +#include + +#include "record.hpp" + +namespace CSMWorld +{ + struct ColumnBase + { + enum Roles + { + Role_Flags = Qt::UserRole, + Role_Display = Qt::UserRole+1 + }; + + enum Flags + { + Flag_Table = 1, // column should be displayed in table view + Flag_Dialogue = 2 // column should be displayed in dialogue view + }; + + enum Display + { + Display_String, + Display_Integer, + Display_Float, + Display_Var, + Display_GmstVarType, + Display_GlobalVarType + }; + + std::string mTitle; + int mFlags; + Display mDisplayType; + + ColumnBase (const std::string& title, Display displayType, int flag); + + virtual ~ColumnBase(); + + virtual bool isEditable() const = 0; + + virtual bool isUserEditable() const; + ///< Can this column be edited directly by the user? + + }; + + template + struct Column : public ColumnBase + { + std::string mTitle; + int mFlags; + + Column (const std::string& title, Display displayType, int flags = Flag_Table | Flag_Dialogue) + : ColumnBase (title, displayType, flags) {} + + virtual QVariant get (const Record& record) const = 0; + + virtual void set (Record& record, const QVariant& data) + { + throw std::logic_error ("Column " + mTitle + " is not editable"); + } + }; +} + +#endif diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp new file mode 100644 index 000000000..018825831 --- /dev/null +++ b/apps/opencs/model/world/columns.hpp @@ -0,0 +1,182 @@ +#ifndef CSM_WOLRD_COLUMNS_H +#define CSM_WOLRD_COLUMNS_H + +#include "columnbase.hpp" + +namespace CSMWorld +{ + template + struct FloatValueColumn : public Column + { + FloatValueColumn() : Column ("Value", ColumnBase::Display_Float) {} + + virtual QVariant get (const Record& record) const + { + return record.get().mValue.getFloat(); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mValue.setFloat (data.toFloat()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct StringIdColumn : public Column + { + StringIdColumn() : Column ("ID", ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return record.get().mId.c_str(); + } + + virtual bool isEditable() const + { + return false; + } + }; + + template + struct RecordStateColumn : public Column + { + RecordStateColumn() : Column ("*", ColumnBase::Display_Integer) {} + + virtual QVariant get (const Record& record) const + { + if (record.mState==Record::State_Erased) + return static_cast (Record::State_Deleted); + + return static_cast (record.mState); + } + + virtual void set (Record& record, const QVariant& data) + { + record.mState = static_cast (data.toInt()); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template + struct FixedRecordTypeColumn : public Column + { + int mType; + + FixedRecordTypeColumn (int type) + : Column ("Record Type", ColumnBase::Display_Integer, 0), mType (type) {} + + virtual QVariant get (const Record& record) const + { + return mType; + } + + virtual bool isEditable() const + { + return false; + } + }; + + /// \attention A var type column must be immediately followed by a suitable value column. + template + struct VarTypeColumn : public Column + { + VarTypeColumn (ColumnBase::Display display) : Column ("Type", display) {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mValue.getType()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mValue.setType (static_cast (data.toInt())); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct VarValueColumn : public Column + { + VarValueColumn() : Column ("Value", ColumnBase::Display_Var) {} + + virtual QVariant get (const Record& record) const + { + switch (record.get().mValue.getType()) + { + case ESM::VT_String: + + return record.get().mValue.getString().c_str(); break; + + case ESM::VT_Int: + case ESM::VT_Short: + case ESM::VT_Long: + + return record.get().mValue.getInteger(); break; + + case ESM::VT_Float: + + return record.get().mValue.getFloat(); break; + + default: return QVariant(); + } + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + switch (record2.mValue.getType()) + { + case ESM::VT_String: + + record2.mValue.setString (data.toString().toUtf8().constData()); + break; + + case ESM::VT_Int: + case ESM::VT_Short: + case ESM::VT_Long: + + record2.mValue.setInteger (data.toInt()); + break; + + case ESM::VT_Float: + + record2.mValue.setFloat (data.toFloat()); + break; + + default: break; + } + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp new file mode 100644 index 000000000..e22ecf992 --- /dev/null +++ b/apps/opencs/model/world/commands.cpp @@ -0,0 +1,108 @@ + +#include "commands.hpp" + +#include + +#include "idtableproxymodel.hpp" +#include "idtable.hpp" + +CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, + const QVariant& new_, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) +{ + mOld = mModel.data (mIndex, Qt::EditRole); + + setText ("Modify " + mModel.headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); +} + +void CSMWorld::ModifyCommand::redo() +{ + mModel.setData (mIndex, mNew); +} + +void CSMWorld::ModifyCommand::undo() +{ + mModel.setData (mIndex, mOld); +} + +CSMWorld::CreateCommand::CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id) +{ + setText (("Create record " + id).c_str()); +} + +void CSMWorld::CreateCommand::redo() +{ + mModel.addRecord (mId); +} + +void CSMWorld::CreateCommand::undo() +{ + mModel.removeRow (mModel.getModelIndex (mId, 0).row()); +} + +CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +{ + setText (("Revert record " + id).c_str()); + + mOld = model.getRecord (id).clone(); +} + +CSMWorld::RevertCommand::~RevertCommand() +{ + delete mOld; +} + +void CSMWorld::RevertCommand::redo() +{ + QModelIndex index = mModel.getModelIndex (mId, 1); + RecordBase::State state = static_cast (mModel.data (index).toInt()); + + if (state==RecordBase::State_ModifiedOnly) + { + mModel.removeRows (index.row(), 1); + } + else + { + mModel.setData (index, static_cast (RecordBase::State_BaseOnly)); + } +} + +void CSMWorld::RevertCommand::undo() +{ + mModel.setRecord (*mOld); +} + +CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +{ + setText (("Delete record " + id).c_str()); + + mOld = model.getRecord (id).clone(); +} + +CSMWorld::DeleteCommand::~DeleteCommand() +{ + delete mOld; +} + +void CSMWorld::DeleteCommand::redo() +{ + QModelIndex index = mModel.getModelIndex (mId, 1); + RecordBase::State state = static_cast (mModel.data (index).toInt()); + + if (state==RecordBase::State_ModifiedOnly) + { + mModel.removeRows (index.row(), 1); + } + else + { + mModel.setData (index, static_cast (RecordBase::State_Deleted)); + } +} + +void CSMWorld::DeleteCommand::undo() +{ + mModel.setRecord (*mOld); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp new file mode 100644 index 000000000..af419215d --- /dev/null +++ b/apps/opencs/model/world/commands.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_WOLRD_COMMANDS_H +#define CSM_WOLRD_COMMANDS_H + +#include "record.hpp" + +#include + +#include +#include +#include + +class QModelIndex; +class QAbstractItemModel; + +namespace CSMWorld +{ + class IdTableProxyModel; + class IdTable; + class RecordBase; + + class ModifyCommand : public QUndoCommand + { + QAbstractItemModel& mModel; + QModelIndex mIndex; + QVariant mNew; + QVariant mOld; + + public: + + ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, + QUndoCommand *parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + + class CreateCommand : public QUndoCommand + { + IdTableProxyModel& mModel; + std::string mId; + + public: + + CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + + class RevertCommand : public QUndoCommand + { + IdTable& mModel; + std::string mId; + RecordBase *mOld; + + // not implemented + RevertCommand (const RevertCommand&); + RevertCommand& operator= (const RevertCommand&); + + public: + + RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual ~RevertCommand(); + + virtual void redo(); + + virtual void undo(); + }; + + class DeleteCommand : public QUndoCommand + { + IdTable& mModel; + std::string mId; + RecordBase *mOld; + + // not implemented + DeleteCommand (const DeleteCommand&); + DeleteCommand& operator= (const DeleteCommand&); + + public: + + DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual ~DeleteCommand(); + + virtual void redo(); + + virtual void undo(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp new file mode 100644 index 000000000..bbd8667b3 --- /dev/null +++ b/apps/opencs/model/world/data.cpp @@ -0,0 +1,113 @@ + +#include "data.hpp" + +#include + +#include + +#include +#include +#include + +#include "idtable.hpp" +#include "columns.hpp" + +void CSMWorld::Data::addModel (QAbstractTableModel *model, UniversalId::Type type1, + UniversalId::Type type2) +{ + mModels.push_back (model); + mModelIndex.insert (std::make_pair (type1, model)); + + if (type2!=UniversalId::Type_None) + mModelIndex.insert (std::make_pair (type2, model)); +} + +CSMWorld::Data::Data() +{ + mGlobals.addColumn (new StringIdColumn); + mGlobals.addColumn (new RecordStateColumn); + mGlobals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Global)); + mGlobals.addColumn (new VarTypeColumn (ColumnBase::Display_GlobalVarType)); + mGlobals.addColumn (new VarValueColumn); + + mGmsts.addColumn (new StringIdColumn); + mGmsts.addColumn (new RecordStateColumn); + mGmsts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Gmst)); + mGmsts.addColumn (new VarTypeColumn (ColumnBase::Display_GmstVarType)); + mGmsts.addColumn (new VarValueColumn); + + addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); + addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); +} + +CSMWorld::Data::~Data() +{ + for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) + delete *iter; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const +{ + return mGlobals; +} + +CSMWorld::IdCollection& CSMWorld::Data::getGlobals() +{ + return mGlobals; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getGmsts() const +{ + return mGmsts; +} + +CSMWorld::IdCollection& CSMWorld::Data::getGmsts() +{ + return mGmsts; +} + +QAbstractTableModel *CSMWorld::Data::getTableModel (const UniversalId& id) +{ + std::map::iterator iter = mModelIndex.find (id.getType()); + + if (iter==mModelIndex.end()) + throw std::logic_error ("No table model available for " + id.toString()); + + return iter->second; +} + +void CSMWorld::Data::merge() +{ + mGlobals.merge(); +} + +void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) +{ + ESM::ESMReader reader; + + /// \todo set encoding properly, once config implementation has been fixed. + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding ("win1252")); + reader.setEncoder (&encoder); + + reader.open (path.string()); + + // Note: We do not need to send update signals here, because at this point the model is not connected + // to any view. + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_GLOB: mGlobals.load (reader, base); break; + case ESM::REC_GMST: mGmsts.load (reader, base); break; + + + default: + + /// \todo throw an exception instead, once all records are implemented + reader.skipRecord(); + } + } +} \ No newline at end of file diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp new file mode 100644 index 000000000..374534651 --- /dev/null +++ b/apps/opencs/model/world/data.hpp @@ -0,0 +1,61 @@ +#ifndef CSM_WOLRD_DATA_H +#define CSM_WOLRD_DATA_H + +#include +#include + +#include + +#include +#include + +#include "idcollection.hpp" +#include "universalid.hpp" + +class QAbstractTableModel; + +namespace CSMWorld +{ + class Data + { + IdCollection mGlobals; + IdCollection mGmsts; + std::vector mModels; + std::map mModelIndex; + + // not implemented + Data (const Data&); + Data& operator= (const Data&); + + void addModel (QAbstractTableModel *model, UniversalId::Type type1, + UniversalId::Type type2 = UniversalId::Type_None); + + public: + + Data(); + + ~Data(); + + const IdCollection& getGlobals() const; + + IdCollection& getGlobals(); + + const IdCollection& getGmsts() const; + + IdCollection& getGmsts(); + + QAbstractTableModel *getTableModel (const UniversalId& id); + ///< If no table model is available for \a id, an exception is thrown. + /// + /// \note The returned table may either be the model for the ID itself or the model that + /// contains the record specified by the ID. + + void merge(); + ///< Merge modified into base. + + void loadFile (const boost::filesystem::path& path, bool base); + ///< Merging content of a file into base or modified. + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp new file mode 100644 index 000000000..5ea953279 --- /dev/null +++ b/apps/opencs/model/world/idcollection.cpp @@ -0,0 +1,6 @@ + +#include "idcollection.hpp" + +CSMWorld::IdCollectionBase::IdCollectionBase() {} + +CSMWorld::IdCollectionBase::~IdCollectionBase() {} diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp new file mode 100644 index 000000000..9b69dfb88 --- /dev/null +++ b/apps/opencs/model/world/idcollection.hpp @@ -0,0 +1,383 @@ +#ifndef CSM_WOLRD_IDCOLLECTION_H +#define CSM_WOLRD_IDCOLLECTION_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "columnbase.hpp" + +namespace CSMWorld +{ + class IdCollectionBase + { + // not implemented + IdCollectionBase (const IdCollectionBase&); + IdCollectionBase& operator= (const IdCollectionBase&); + + public: + + IdCollectionBase(); + + virtual ~IdCollectionBase(); + + virtual int getSize() const = 0; + + virtual std::string getId (int index) const = 0; + + virtual int getIndex (const std::string& id) const = 0; + + virtual int getColumns() const = 0; + + virtual const ColumnBase& getColumn (int column) const = 0; + + virtual QVariant getData (int index, int column) const = 0; + + virtual void setData (int index, int column, const QVariant& data) = 0; + + virtual void merge() = 0; + ///< Merge modified into base. + + virtual void purge() = 0; + ///< Remove records that are flagged as erased. + + virtual void removeRows (int index, int count) = 0; + + virtual void appendBlankRecord (const std::string& id) = 0; + + virtual int searchId (const std::string& id) const = 0; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const = 0; + ///< Return ID for \a record. + /// + /// \attention Throws an exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const = 0; + + virtual void load (ESM::ESMReader& reader, bool base) = 0; + }; + + ///< \brief Collection of ID-based records + template + class IdCollection : public IdCollectionBase + { + std::vector > mRecords; + std::map mIndex; + std::vector *> mColumns; + + // not implemented + IdCollection (const IdCollection&); + IdCollection& operator= (const IdCollection&); + + public: + + IdCollection(); + + virtual ~IdCollection(); + + void add (const ESXRecordT& record); + ///< Add a new record (modified) + + virtual int getSize() const; + + virtual std::string getId (int index) const; + + virtual int getIndex (const std::string& id) const; + + virtual int getColumns() const; + + virtual QVariant getData (int index, int column) const; + + virtual void setData (int index, int column, const QVariant& data); + + virtual const ColumnBase& getColumn (int column) const; + + virtual void merge(); + ///< Merge modified into base. + + virtual void purge(); + ///< Remove records that are flagged as erased. + + virtual void removeRows (int index, int count) ; + + virtual void appendBlankRecord (const std::string& id); + + virtual int searchId (const std::string& id) const; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const; + ///< Return ID for \a record. + /// + /// \attention Throw san exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const; + + virtual void load (ESM::ESMReader& reader, bool base); + + void addColumn (Column *column); + }; + + template + IdCollection::IdCollection() + {} + + template + IdCollection::~IdCollection() + { + for (typename std::vector *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) + delete *iter; + } + + template + void IdCollection::add (const ESXRecordT& record) + { + std::string id = Misc::StringUtils::lowerCase(record.mId); + + std::map::iterator iter = mIndex.find (id); + + if (iter==mIndex.end()) + { + Record record2; + record2.mState = Record::State_ModifiedOnly; + record2.mModified = record; + + mRecords.push_back (record2); + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1)); + } + else + { + mRecords[iter->second].setModified (record); + } + } + + template + int IdCollection::getSize() const + { + return mRecords.size(); + } + + template + std::string IdCollection::getId (int index) const + { + return mRecords.at (index).get().mId; + } + + template + int IdCollection::getIndex (const std::string& id) const + { + int index = searchId (id); + + if (index==-1) + throw std::runtime_error ("invalid ID: " + id); + + return index; + } + + template + int IdCollection::getColumns() const + { + return mColumns.size(); + } + + template + QVariant IdCollection::getData (int index, int column) const + { + return mColumns.at (column)->get (mRecords.at (index)); + } + + template + void IdCollection::setData (int index, int column, const QVariant& data) + { + return mColumns.at (column)->set (mRecords.at (index), data); + } + + template + const ColumnBase& IdCollection::getColumn (int column) const + { + return *mColumns.at (column); + } + + template + void IdCollection::addColumn (Column *column) + { + mColumns.push_back (column); + } + + template + void IdCollection::merge() + { + for (typename std::vector >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) + iter->merge(); + + purge(); + } + + template + void IdCollection::purge() + { + mRecords.erase (std::remove_if (mRecords.begin(), mRecords.end(), + std::mem_fun_ref (&Record::isErased) // I want lambda :( + ), mRecords.end()); + } + + template + void IdCollection::removeRows (int index, int count) + { + mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count); + + typename std::map::iterator iter = mIndex.begin(); + + while (iter!=mIndex.end()) + { + if (iter->second>=index) + { + if (iter->second>=index+count) + { + iter->second -= count; + } + else + { + mIndex.erase (iter++); + } + } + + ++iter; + } + } + + template + void IdCollection::appendBlankRecord (const std::string& id) + { + ESXRecordT record; + record.mId = id; + record.blank(); + add (record); + } + + template + int IdCollection::searchId (const std::string& id) const + { + std::string id2 = Misc::StringUtils::lowerCase(id); + + std::map::const_iterator iter = mIndex.find (id2); + + if (iter==mIndex.end()) + return -1; + + return iter->second; + } + + template + void IdCollection::replace (int index, const RecordBase& record) + { + mRecords.at (index) = dynamic_cast&> (record); + } + + template + void IdCollection::appendRecord (const RecordBase& record) + { + mRecords.push_back (dynamic_cast&> (record)); + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (getId (record)), mRecords.size()-1)); + } + + template + std::string IdCollection::getId (const RecordBase& record) const + { + const Record& record2 = dynamic_cast&> (record); + return (record2.isModified() ? record2.mModified : record2.mBase).mId; + } + + template + void IdCollection::load (ESM::ESMReader& reader, bool base) + { + std::string id = reader.getHNOString ("NAME"); + + int index = searchId (id); + + if (reader.isNextSub ("DELE")) + { + reader.skipRecord(); + + if (index==-1) + { + // deleting a record that does not exist + + // ignore it for now + + /// \todo report the problem to the user + } + else if (base) + { + removeRows (index, 1); + } + else + { + mRecords[index].mState = RecordBase::State_Deleted; + } + } + else + { + ESXRecordT record; + record.mId = id; + record.load (reader); + + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + appendRecord (record2); + } + else + { + // old record + Record& record2 = mRecords[index]; + + if (base) + record2.mBase = record; + else + record2.setModified (record); + } + } + } + + template + const RecordBase& IdCollection::getRecord (const std::string& id) const + { + int index = getIndex (id); + return mRecords.at (index); + } +} + +#endif diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp new file mode 100644 index 000000000..afed6b6ed --- /dev/null +++ b/apps/opencs/model/world/idtable.cpp @@ -0,0 +1,140 @@ + +#include "idtable.hpp" + +#include "idcollection.hpp" + +CSMWorld::IdTable::IdTable (IdCollectionBase *idCollection) : mIdCollection (idCollection) +{ + +} + +CSMWorld::IdTable::~IdTable() +{ + +} + +int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mIdCollection->getSize(); +} + +int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mIdCollection->getColumns(); +} + +QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const +{ + if (role!=Qt::DisplayRole && role!=Qt::EditRole) + return QVariant(); + + if (role==Qt::EditRole && !mIdCollection->getColumn (index.column()).isEditable()) + return QVariant(); + + return mIdCollection->getData (index.row(), index.column()); +} + +QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (orientation==Qt::Vertical) + return QVariant(); + + if (role==Qt::DisplayRole) + return tr (mIdCollection->getColumn (section).mTitle.c_str()); + + if (role==ColumnBase::Role_Flags) + return mIdCollection->getColumn (section).mFlags; + + if (role==ColumnBase::Role_Display) + return mIdCollection->getColumn (section).mDisplayType; + + return QVariant(); +} + +bool CSMWorld::IdTable::setData ( const QModelIndex &index, const QVariant &value, int role) +{ + if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) + { + mIdCollection->setData (index.row(), index.column(), value); + + emit dataChanged (CSMWorld::IdTable::index (index.row(), 0), + CSMWorld::IdTable::index (index.row(), mIdCollection->getColumns()-1)); + + return true; + } + + return false; +} + +Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const +{ + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + if (mIdCollection->getColumn (index.column()).isUserEditable()) + flags |= Qt::ItemIsEditable; + + return flags; +} + +bool CSMWorld::IdTable::removeRows (int row, int count, const QModelIndex& parent) +{ + if (parent.isValid()) + return false; + + beginRemoveRows (parent, row, row+count-1); + + mIdCollection->removeRows (row, count); + + endRemoveRows(); + + return true; +} + +void CSMWorld::IdTable::addRecord (const std::string& id) +{ + int index = mIdCollection->getSize(); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id); + + endInsertRows(); +} + +QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const +{ + return index (mIdCollection->getIndex (id), column); +} + +void CSMWorld::IdTable::setRecord (const RecordBase& record) +{ + int index = mIdCollection->searchId (mIdCollection->getId (record)); + + if (index==-1) + { + int index = mIdCollection->getSize(); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendRecord (record); + + endInsertRows(); + } + else + { + mIdCollection->replace (index, record); + emit dataChanged (CSMWorld::IdTable::index (index, 0), + CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); + } +} + +const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const +{ + return mIdCollection->getRecord (id); +} \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp new file mode 100644 index 000000000..deaebaa38 --- /dev/null +++ b/apps/opencs/model/world/idtable.hpp @@ -0,0 +1,53 @@ +#ifndef CSM_WOLRD_IDTABLE_H +#define CSM_WOLRD_IDTABLE_H + +#include + +namespace CSMWorld +{ + class IdCollectionBase; + class RecordBase; + + class IdTable : public QAbstractTableModel + { + Q_OBJECT + + IdCollectionBase *mIdCollection; + + // not implemented + IdTable (const IdTable&); + IdTable& operator= (const IdTable&); + + public: + + IdTable (IdCollectionBase *idCollection); + ///< The ownership of \a idCollection is not transferred. + + virtual ~IdTable(); + + virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; + + virtual int columnCount (const QModelIndex & parent = QModelIndex()) const; + + virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + virtual bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + virtual Qt::ItemFlags flags (const QModelIndex & index) const; + + virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); + + void addRecord (const std::string& id); + + QModelIndex getModelIndex (const std::string& id, int column) const; + + void setRecord (const RecordBase& record); + ///< Add record or overwrite existing recrod. + + const RecordBase& getRecord (const std::string& id) const; + }; +} + +#endif diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp new file mode 100644 index 000000000..78995f60b --- /dev/null +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -0,0 +1,18 @@ + +#include "idtableproxymodel.hpp" + +#include "idtable.hpp" + +CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) +: QSortFilterProxyModel (parent) +{} + +void CSMWorld::IdTableProxyModel::addRecord (const std::string& id) +{ + dynamic_cast (*sourceModel()).addRecord (id); +} + +QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const +{ + return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); +} \ No newline at end of file diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp new file mode 100644 index 000000000..3f1537cce --- /dev/null +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H +#define CSM_WOLRD_IDTABLEPROXYMODEL_H + +#include + +#include + +namespace CSMWorld +{ + class IdTableProxyModel : public QSortFilterProxyModel + { + Q_OBJECT + + public: + + IdTableProxyModel (QObject *parent = 0); + + virtual void addRecord (const std::string& id); + + virtual QModelIndex getModelIndex (const std::string& id, int column) const; + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp new file mode 100644 index 000000000..14f63c155 --- /dev/null +++ b/apps/opencs/model/world/record.cpp @@ -0,0 +1,21 @@ + +#include "record.hpp" + +CSMWorld::RecordBase::~RecordBase() {} + +bool CSMWorld::RecordBase::isDeleted() const +{ + return mState==State_Deleted || mState==State_Erased; +} + + +bool CSMWorld::RecordBase::isErased() const +{ + return mState==State_Erased; +} + + +bool CSMWorld::RecordBase::isModified() const +{ + return mState==State_Modified || mState==State_ModifiedOnly; +} \ No newline at end of file diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp new file mode 100644 index 000000000..53bb7ea2c --- /dev/null +++ b/apps/opencs/model/world/record.hpp @@ -0,0 +1,104 @@ +#ifndef CSM_WOLRD_RECORD_H +#define CSM_WOLRD_RECORD_H + +#include + +namespace CSMWorld +{ + struct RecordBase + { + enum State + { + State_BaseOnly = 0, // defined in base only + State_Modified = 1, // exists in base, but has been modified + State_ModifiedOnly = 2, // newly created in modified + State_Deleted = 3, // exists in base, but has been deleted + State_Erased = 4 // does not exist at all (we mostly treat that the same way as deleted) + }; + + State mState; + + virtual ~RecordBase(); + + virtual RecordBase *clone() const = 0; + + bool isDeleted() const; + + bool isErased() const; + + bool isModified() const; + }; + + template + struct Record : public RecordBase + { + ESXRecordT mBase; + ESXRecordT mModified; + + virtual RecordBase *clone() const; + + const ESXRecordT& get() const; + ///< Throws an exception, if the record is deleted. + + const ESXRecordT& getBase() const; + ///< Throws an exception, if the record is deleted. Returns modified, if there is no base. + + void setModified (const ESXRecordT& modified); + ///< Throws an exception, if the record is deleted. + + void merge(); + ///< Merge modified into base. + }; + + template + RecordBase *Record::clone() const + { + return new Record (*this); + } + + template + const ESXRecordT& Record::get() const + { + if (mState==State_Erased) + throw std::logic_error ("attempt to access a deleted record"); + + return mState==State_BaseOnly ? mBase : mModified; + } + + template + const ESXRecordT& Record::getBase() const + { + if (mState==State_Erased) + throw std::logic_error ("attempt to access a deleted record"); + + return mState==State_ModifiedOnly ? mModified : mBase; + } + + template + void Record::setModified (const ESXRecordT& modified) + { + if (mState==State_Erased) + throw std::logic_error ("attempt to modify a deleted record"); + + mModified = modified; + + if (mState!=State_ModifiedOnly) + mState = mBase==mModified ? State_BaseOnly : State_Modified; + } + + template + void Record::merge() + { + if (isModified()) + { + mBase = mModified; + mState = State_BaseOnly; + } + else if (mState==State_Deleted) + { + mState = State_Erased; + } + } +} + +#endif diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp new file mode 100644 index 000000000..c006852bc --- /dev/null +++ b/apps/opencs/model/world/universalid.cpp @@ -0,0 +1,239 @@ + +#include "universalid.hpp" + +#include +#include +#include + +namespace +{ + struct TypeData + { + CSMWorld::UniversalId::Class mClass; + CSMWorld::UniversalId::Type mType; + const char *mName; + }; + + static const TypeData sNoArg[] = + { + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; + + static const TypeData sIdArg[] = + { + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; + + static const TypeData sIndexArg[] = + { + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; +} + +CSMWorld::UniversalId::UniversalId (const std::string& universalId) +{ + std::string::size_type index = universalId.find (':'); + + if (index==std::string::npos) + { + std::string type = universalId.substr (0, index); + + if (index==std::string::npos) + { + for (int i=0; sNoArg[i].mName; ++i) + if (type==sNoArg[i].mName) + { + mArgumentType = ArgumentType_None; + mType = sNoArg[i].mType; + mClass = sNoArg[i].mClass; + return; + } + } + else + { + for (int i=0; sIdArg[i].mName; ++i) + if (type==sIdArg[i].mName) + { + mArgumentType = ArgumentType_Id; + mType = sIdArg[i].mType; + mClass = sIdArg[i].mClass; + mId = universalId.substr (0, index); + return; + } + + for (int i=0; sIndexArg[i].mName; ++i) + if (type==sIndexArg[i].mName) + { + mArgumentType = ArgumentType_Index; + mType = sIndexArg[i].mType; + mClass = sIndexArg[i].mClass; + + std::istringstream stream (universalId.substr (0, index)); + + if (stream >> mIndex) + return; + + break; + } + } + } + + throw std::runtime_error ("invalid UniversalId: " + universalId); +} + +CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_None), mType (type), mIndex (0) +{ + for (int i=0; sNoArg[i].mName; ++i) + if (type==sNoArg[i].mType) + { + mClass = sNoArg[i].mClass; + return; + } + + throw std::logic_error ("invalid argument-less UniversalId type"); +} + +CSMWorld::UniversalId::UniversalId (Type type, const std::string& id) +: mArgumentType (ArgumentType_Id), mType (type), mId (id), mIndex (0) +{ + for (int i=0; sIdArg[i].mName; ++i) + if (type==sIdArg[i].mType) + { + mClass = sIdArg[i].mClass; + return; + } + + throw std::logic_error ("invalid ID argument UniversalId type"); +} + +CSMWorld::UniversalId::UniversalId (Type type, int index) +: mArgumentType (ArgumentType_Index), mType (type), mIndex (index) +{ + for (int i=0; sIndexArg[i].mName; ++i) + if (type==sIndexArg[i].mType) + { + mClass = sIndexArg[i].mClass; + return; + } + + throw std::logic_error ("invalid index argument UniversalId type"); +} + +CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const +{ + return mClass; +} + +CSMWorld::UniversalId::ArgumentType CSMWorld::UniversalId::getArgumentType() const +{ + return mArgumentType; +} + +CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const +{ + return mType; +} + +const std::string& CSMWorld::UniversalId::getId() const +{ + if (mArgumentType!=ArgumentType_Id) + throw std::logic_error ("invalid access to ID of non-ID UniversalId"); + + return mId; +} + +int CSMWorld::UniversalId::getIndex() const +{ + if (mArgumentType!=ArgumentType_Index) + throw std::logic_error ("invalid access to index of non-index UniversalId"); + + return mIndex; +} + +bool CSMWorld::UniversalId::isEqual (const UniversalId& universalId) const +{ + if (mClass!=universalId.mClass || mArgumentType!=universalId.mArgumentType || mType!=universalId.mType) + return false; + + switch (mArgumentType) + { + case ArgumentType_Id: return mId==universalId.mId; + case ArgumentType_Index: return mIndex==universalId.mIndex; + + default: return true; + } +} + +bool CSMWorld::UniversalId::isLess (const UniversalId& universalId) const +{ + if (mTypeuniversalId.mType) + return false; + + switch (mArgumentType) + { + case ArgumentType_Id: return mId +#include + +#include + +namespace CSMWorld +{ + class UniversalId + { + public: + + enum Class + { + Class_None = 0, + Class_Record, + Class_SubRecord, + Class_RecordList, + Class_Collection, // multiple types of records combined + Class_Transient, // not part of the world data or the project data + Class_NonRecord // record like data that is not part of the world + }; + + enum ArgumentType + { + ArgumentType_None, + ArgumentType_Id, + ArgumentType_Index + }; + + enum Type + { + Type_None, + Type_Globals, + Type_Global, + Type_VerificationResults, + Type_Gmsts, + Type_Gmst + + }; + + private: + + Class mClass; + ArgumentType mArgumentType; + Type mType; + std::string mId; + int mIndex; + + public: + + UniversalId (const std::string& universalId); + + UniversalId (Type type = Type_None); + ///< Using a type for a non-argument-less UniversalId will throw an exception. + + UniversalId (Type type, const std::string& id); + ///< Using a type for a non-ID-argument UniversalId will throw an exception. + + UniversalId (Type type, int index); + ///< Using a type for a non-index-argument UniversalId will throw an exception. + + Class getClass() const; + + ArgumentType getArgumentType() const; + + Type getType() const; + + const std::string& getId() const; + ///< Calling this function for a non-ID type will throw an exception. + + int getIndex() const; + ///< Calling this function for a non-index type will throw an exception. + + bool isEqual (const UniversalId& universalId) const; + + bool isLess (const UniversalId& universalId) const; + + std::string getTypeName() const; + + std::string toString() const; + }; + + bool operator== (const UniversalId& left, const UniversalId& right); + bool operator!= (const UniversalId& left, const UniversalId& right); + + bool operator< (const UniversalId& left, const UniversalId& right); + + std::ostream& operator< (std::ostream& stream, const UniversalId& universalId); +} + +Q_DECLARE_METATYPE (CSMWorld::UniversalId) + +#endif diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp new file mode 100644 index 000000000..f956317a7 --- /dev/null +++ b/apps/opencs/view/doc/filedialog.cpp @@ -0,0 +1,272 @@ +#include "filedialog.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +FileDialog::FileDialog(QWidget *parent) : + QDialog(parent) +{ + setupUi(this); + + // Models + mDataFilesModel = new DataFilesModel(this); + + mMastersProxyModel = new QSortFilterProxyModel(); + mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm")); + mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mMastersProxyModel->setSourceModel(mDataFilesModel); + + mPluginsProxyModel = new PluginsProxyModel(); + mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp")); + mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mPluginsProxyModel->setSourceModel(mDataFilesModel); + + mFilterProxyModel = new QSortFilterProxyModel(); + mFilterProxyModel->setDynamicSortFilter(true); + mFilterProxyModel->setSourceModel(mPluginsProxyModel); + + QCheckBox checkBox; + unsigned int height = checkBox.sizeHint().height() + 4; + + mastersTable->setModel(mMastersProxyModel); + mastersTable->setObjectName("MastersTable"); + mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); + mastersTable->setSortingEnabled(false); + mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mastersTable->setAlternatingRowColors(true); + mastersTable->horizontalHeader()->setStretchLastSection(true); + + // Set the row height to the size of the checkboxes + mastersTable->verticalHeader()->setDefaultSectionSize(height); + mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + mastersTable->verticalHeader()->hide(); + + pluginsTable->setModel(mFilterProxyModel); + pluginsTable->setObjectName("PluginsTable"); + pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); + pluginsTable->setSortingEnabled(false); + pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + pluginsTable->setAlternatingRowColors(true); + pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); + pluginsTable->horizontalHeader()->setStretchLastSection(true); + + pluginsTable->verticalHeader()->setDefaultSectionSize(height); + pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + + // Hide the profile elements + profileLabel->hide(); + profilesComboBox->hide(); + newProfileButton->hide(); + deleteProfileButton->hide(); + + // Add some extra widgets + QHBoxLayout *nameLayout = new QHBoxLayout(); + QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + mNameLabel = new QLabel(tr("File Name:"), this); + + QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$")); + mNameLineEdit = new LineEdit(this); + mNameLineEdit->setValidator(validator); + + nameLayout->addSpacerItem(spacer); + nameLayout->addWidget(mNameLabel); + nameLayout->addWidget(mNameLineEdit); + + mButtonBox = new QDialogButtonBox(this); + + mCreateButton = new QPushButton(tr("Create"), this); + mCreateButton->setEnabled(false); + + verticalLayout->addLayout(nameLayout); + verticalLayout->addWidget(mButtonBox); + + // Set sizes + QList sizeList; + sizeList << 175; + sizeList << 200; + + splitter->setSizes(sizeList); + + resize(600, 400); + + connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); + connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); + + connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + + connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + + connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); + + connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void FileDialog::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + mastersTable->setColumnHidden(1, true); + mastersTable->setColumnHidden(3, true); + mastersTable->setColumnHidden(4, true); + mastersTable->setColumnHidden(5, true); + mastersTable->setColumnHidden(6, true); + mastersTable->setColumnHidden(7, true); + mastersTable->setColumnHidden(8, true); + mastersTable->resizeColumnsToContents(); + + pluginsTable->setColumnHidden(1, true); + pluginsTable->setColumnHidden(3, true); + pluginsTable->setColumnHidden(4, true); + pluginsTable->setColumnHidden(5, true); + pluginsTable->setColumnHidden(6, true); + pluginsTable->setColumnHidden(7, true); + pluginsTable->setColumnHidden(8, true); + pluginsTable->resizeColumnsToContents(); + +} + +void FileDialog::updateOpenButton(const QStringList &items) +{ + QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); + + if (!openButton) + return; + + openButton->setEnabled(!items.isEmpty()); +} + +void FileDialog::updateCreateButton(const QString &name) +{ + if (!mCreateButton->isVisible()) + return; + + mCreateButton->setEnabled(!name.isEmpty()); +} + +void FileDialog::filterChanged(const QString &filter) +{ + QRegExp filterRe(filter, Qt::CaseInsensitive, QRegExp::FixedString); + mFilterProxyModel->setFilterRegExp(filterRe); +} + +void FileDialog::addFiles(const QString &path) +{ + mDataFilesModel->addFiles(path); + mDataFilesModel->sort(3); // Sort by date accessed +} + +void FileDialog::setEncoding(const QString &encoding) +{ + mDataFilesModel->setEncoding(encoding); +} + +void FileDialog::setCheckState(QModelIndex index) +{ + if (!index.isValid()) + return; + + QObject *object = QObject::sender(); + + // Not a signal-slot call + if (!object) + return; + + + if (object->objectName() == QLatin1String("PluginsTable")) { + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( + mFilterProxyModel->mapToSource(index)); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } + } + + if (object->objectName() == QLatin1String("MastersTable")) { + QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } + } + + return; +} + +QStringList FileDialog::checkedItemsPaths() +{ + return mDataFilesModel->checkedItemsPaths(); +} + +QString FileDialog::fileName() +{ + return mNameLineEdit->text(); +} + +void FileDialog::openFile() +{ + setWindowTitle(tr("Open")); + + mNameLabel->hide(); + mNameLineEdit->hide(); + mCreateButton->hide(); + + mButtonBox->removeButton(mCreateButton); + mButtonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Open); + QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); + openButton->setEnabled(false); + + show(); + raise(); + activateWindow(); +} + +void FileDialog::newFile() +{ + setWindowTitle(tr("New")); + + mNameLabel->show(); + mNameLineEdit->clear(); + mNameLineEdit->show(); + mCreateButton->show(); + + mButtonBox->setStandardButtons(QDialogButtonBox::Cancel); + mButtonBox->addButton(mCreateButton, QDialogButtonBox::ActionRole); + + show(); + raise(); + activateWindow(); +} + +void FileDialog::accept() +{ + emit openFiles(); +} + +void FileDialog::createButtonClicked() +{ + emit createNewFile(); +} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp new file mode 100644 index 000000000..b21618d5d --- /dev/null +++ b/apps/opencs/view/doc/filedialog.hpp @@ -0,0 +1,66 @@ +#ifndef FILEDIALOG_HPP +#define FILEDIALOG_HPP + +#include +#include + +#include "ui_datafilespage.h" + +class QDialogButtonBox; +class QSortFilterProxyModel; +class QAbstractItemModel; +class QPushButton; +class QStringList; +class QString; +class QMenu; + +class DataFilesModel; +class PluginsProxyModel; + +class FileDialog : public QDialog, private Ui::DataFilesPage +{ + Q_OBJECT +public: + explicit FileDialog(QWidget *parent = 0); + void addFiles(const QString &path); + void setEncoding(const QString &encoding); + + void openFile(); + void newFile(); + void accepted(); + + QStringList checkedItemsPaths(); + QString fileName(); + +signals: + void openFiles(); + void createNewFile(); + +public slots: + void accept(); + +private slots: + void updateViews(); + void updateOpenButton(const QStringList &items); + void updateCreateButton(const QString &name); + void setCheckState(QModelIndex index); + + void filterChanged(const QString &filter); + + void createButtonClicked(); + +private: + QLabel *mNameLabel; + LineEdit *mNameLineEdit; + + QPushButton *mCreateButton; + QDialogButtonBox *mButtonBox; + + DataFilesModel *mDataFilesModel; + + PluginsProxyModel *mPluginsProxyModel; + QSortFilterProxyModel *mMastersProxyModel; + QSortFilterProxyModel *mFilterProxyModel; +}; + +#endif // FILEDIALOG_HPP diff --git a/apps/opencs/view/doc/opendialog.cpp b/apps/opencs/view/doc/opendialog.cpp new file mode 100644 index 000000000..7b62aafa3 --- /dev/null +++ b/apps/opencs/view/doc/opendialog.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include + +#include "opendialog.hpp" + +OpenDialog::OpenDialog(QWidget * parent) : QDialog(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + mFileSelector = new DataFilesList(mCfgMgr, this); + layout->addWidget(mFileSelector); + + /// \todo move config to Editor class and add command line options. + // We use the Configuration Manager to retrieve the configuration values + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) + ("data-local", boost::program_options::value()->default_value("")) + ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) + ("encoding", boost::program_options::value()->default_value("win1252")); + + boost::program_options::notify(variables); + + mCfgMgr.readConfiguration(variables, desc); + + Files::PathContainer mDataDirs, mDataLocal; + if (!variables["data"].empty()) { + mDataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + mDataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mCfgMgr.processPaths(mDataDirs); + mCfgMgr.processPaths(mDataLocal); + + // Set the charset for reading the esm/esp files + QString encoding = QString::fromStdString(variables["encoding"].as()); + + Files::PathContainer dataDirs; + dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end()); + dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end()); + mFileSelector->setupDataFiles(dataDirs, encoding); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel, Qt::Horizontal, this); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + layout->addWidget(buttonBox); + + setLayout(layout); + setWindowTitle(tr("Open")); +} + +void OpenDialog::getFileList(std::vector& paths) +{ + mFileSelector->selectedFiles(paths); +} diff --git a/apps/opencs/view/doc/opendialog.hpp b/apps/opencs/view/doc/opendialog.hpp new file mode 100644 index 000000000..6355aea44 --- /dev/null +++ b/apps/opencs/view/doc/opendialog.hpp @@ -0,0 +1,17 @@ +#include +#include + +class DataFilesList; +class QDialogButtonBox; + +class OpenDialog : public QDialog { + Q_OBJECT +public: + OpenDialog(QWidget * parent = 0); + + void getFileList(std::vector& paths); +private: + DataFilesList * mFileSelector; + QDialogButtonBox * buttonBox; + Files::ConfigurationManager mCfgMgr; +}; \ No newline at end of file diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp new file mode 100644 index 000000000..6977d7953 --- /dev/null +++ b/apps/opencs/view/doc/operation.cpp @@ -0,0 +1,151 @@ +#include "operation.hpp" + +#include + +#include +#include +#include + +#include "../../model/doc/document.hpp" + +void CSVDoc::Operation::updateLabel (int threads) +{ + if (threads==-1 || ((threads==0)!=mStalling)) + { + std::string name ("unknown operation"); + + switch (mType) + { + case CSMDoc::State_Saving: name = "saving"; break; + case CSMDoc::State_Verifying: name = "verifying"; break; + } + + std::ostringstream stream; + + if ((mStalling = (threads<=0))) + { + stream << name << " (waiting for a free worker thread)"; + } + else + { + stream << name << " (%p%)"; + } + + mProgressBar->setFormat (stream.str().c_str()); + } +} + +CSVDoc::Operation::Operation (int type, QWidget* parent) : mType (type), mStalling (false) +{ + /// \todo Add a cancel button or a pop up menu with a cancel item + initWidgets(); + setBarColor( type); + updateLabel(); + + /// \todo assign different progress bar colours to allow the user to distinguish easily between operation types +} + +CSVDoc::Operation::~Operation() +{ + delete mLayout; + delete mProgressBar; + delete mAbortButton; +} + +void CSVDoc::Operation::initWidgets() +{ + mProgressBar = new QProgressBar (); + mAbortButton = new QPushButton("Abort"); + mLayout = new QHBoxLayout(); + + mLayout->addWidget (mProgressBar); + mLayout->addWidget (mAbortButton); + + connect (mAbortButton, SIGNAL (clicked()), this, SLOT (abortOperation())); +} + +void CSVDoc::Operation::setProgress (int current, int max, int threads) +{ + updateLabel (threads); + mProgressBar->setRange (0, max); + mProgressBar->setValue (current); +} + +int CSVDoc::Operation::getType() const +{ + return mType; +} + +void CSVDoc::Operation::setBarColor (int type) +{ + QString style ="QProgressBar {" + "text-align: center;" + "}" + "QProgressBar::chunk {" + "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:.50 %2 stop: .51 %3 stop:1 %4);" + "text-align: center;" + "margin: 2px 1px 1p 2px;" + "}"; + + QString topColor = "#F2F6F8"; + QString bottomColor = "#E0EFF9"; + QString midTopColor = "#D8E1E7"; + QString midBottomColor = "#B5C6D0"; // default gray gloss + + // colors inspired by samples from: + // http://www.colorzilla.com/gradient-editor/ + + switch (type) + { + case CSMDoc::State_Saving: + + topColor = "#FECCB1"; + midTopColor = "#F17432"; + midBottomColor = "#EA5507"; + bottomColor = "#FB955E"; // red gloss #2 + break; + + case CSMDoc::State_Searching: + + topColor = "#EBF1F6"; + midTopColor = "#ABD3EE"; + midBottomColor = "#89C3EB"; + bottomColor = "#D5EBFB"; //blue gloss #4 + break; + + case CSMDoc::State_Verifying: + + topColor = "#BFD255"; + midTopColor = "#8EB92A"; + midBottomColor = "#72AA00"; + bottomColor = "#9ECB2D"; //green gloss + break; + + case CSMDoc::State_Compiling: + + topColor = "#F3E2C7"; + midTopColor = "#C19E67"; + midBottomColor = "#B68D4C"; + bottomColor = "#E9D4B3"; //l Brown 3D + break; + + default: + + topColor = "#F2F6F8"; + bottomColor = "#E0EFF9"; + midTopColor = "#D8E1E7"; + midBottomColor = "#B5C6D0"; // gray gloss for undefined ops + } + + mProgressBar->setStyleSheet(style.arg(topColor).arg(midTopColor).arg(midBottomColor).arg(bottomColor)); +} + +QHBoxLayout *CSVDoc::Operation::getLayout() const +{ + return mLayout; +} + +void CSVDoc::Operation::abortOperation() +{ + emit abortOperation (mType); +} diff --git a/apps/opencs/view/doc/operation.hpp b/apps/opencs/view/doc/operation.hpp new file mode 100644 index 000000000..48839fada --- /dev/null +++ b/apps/opencs/view/doc/operation.hpp @@ -0,0 +1,53 @@ +#ifndef CSV_DOC_OPERATION_H +#define CSV_DOC_OPERATION_H + +#include + +class QHBoxLayout; +class QPushButton; +class QProgressBar; + +namespace CSVDoc +{ + class Operation : public QObject + { + Q_OBJECT + + int mType; + bool mStalling; + QProgressBar *mProgressBar; + QPushButton *mAbortButton; + QHBoxLayout *mLayout; + + // not implemented + Operation (const Operation&); + Operation& operator= (const Operation&); + + void updateLabel (int threads = -1); + + public: + + Operation (int type, QWidget *parent); + ~Operation(); + + void setProgress (int current, int max, int threads); + + int getType() const; + QHBoxLayout *getLayout() const; + + private: + + void setBarColor (int type); + void initWidgets(); + + signals: + + void abortOperation (int type); + + private slots: + + void abortOperation(); + }; +} + +#endif diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp new file mode 100644 index 000000000..ce001afaa --- /dev/null +++ b/apps/opencs/view/doc/operations.cpp @@ -0,0 +1,69 @@ +#include "operations.hpp" + +#include +#include + +#include "operation.hpp" + +CSVDoc::Operations::Operations() +{ + /// \todo make widget height fixed (exactly the height required to display all operations) + + setFeatures (QDockWidget::NoDockWidgetFeatures); + + QWidget *widgetContainer = new QWidget (this); + mLayout = new QVBoxLayout; + + widgetContainer->setLayout (mLayout); + setWidget (widgetContainer); + setVisible (false); + setFixedHeight (widgetContainer->height()); + setTitleBarWidget (new QWidget (this)); +} + +void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) +{ + for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) + if ((*iter)->getType()==type) + { + (*iter)->setProgress (current, max, threads); + return; + } + + int oldCount = mOperations.size(); + int newCount = oldCount + 1; + + Operation *operation = new Operation (type, this); + connect (operation, SIGNAL (abortOperation (int)), this, SIGNAL (abortOperation (int))); + + mLayout->addLayout (operation->getLayout()); + mOperations.push_back (operation); + operation->setProgress (current, max, threads); + + if ( oldCount > 0) + setFixedHeight (height()/oldCount * newCount); + + setVisible (true); +} + +void CSVDoc::Operations::quitOperation (int type) +{ + for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) + if ((*iter)->getType()==type) + { + int oldCount = mOperations.size(); + int newCount = oldCount - 1; + + mLayout->removeItem ((*iter)->getLayout()); + + delete *iter; + mOperations.erase (iter); + + if (oldCount > 1) + setFixedHeight (height() / oldCount * newCount); + else + setVisible (false); + + break; + } +} diff --git a/apps/opencs/view/doc/operations.hpp b/apps/opencs/view/doc/operations.hpp new file mode 100644 index 000000000..71c595f66 --- /dev/null +++ b/apps/opencs/view/doc/operations.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_DOC_OPERATIONS_H +#define CSV_DOC_OPERATIONS_H + +#include + +#include + +class QVBoxLayout; + +namespace CSVDoc +{ + class Operation; + + class Operations : public QDockWidget + { + Q_OBJECT + + QVBoxLayout *mLayout; + std::vector mOperations; + + // not implemented + Operations (const Operations&); + Operations& operator= (const Operations&); + + public: + + Operations(); + + void setProgress (int current, int max, int type, int threads); + ///< Implicitly starts the operation, if it is not running already. + + void quitOperation (int type); + ///< Calling this function for an operation that is not running is a no-op. + + signals: + + void abortOperation (int type); + }; +} + +#endif diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp new file mode 100644 index 000000000..7861a1c2e --- /dev/null +++ b/apps/opencs/view/doc/startup.cpp @@ -0,0 +1,20 @@ + +#include "startup.hpp" + +#include +#include + +CSVDoc::StartupDialogue::StartupDialogue() +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + QPushButton *createDocument = new QPushButton ("new", this); + connect (createDocument, SIGNAL (clicked()), this, SIGNAL (createDocument())); + layout->addWidget (createDocument); + + QPushButton *loadDocument = new QPushButton ("load", this); + connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument())); + layout->addWidget (loadDocument); + + setLayout (layout); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp new file mode 100644 index 000000000..f24d2a64b --- /dev/null +++ b/apps/opencs/view/doc/startup.hpp @@ -0,0 +1,24 @@ +#ifndef CSV_DOC_STARTUP_H +#define CSV_DOC_STARTUP_H + +#include + +namespace CSVDoc +{ + class StartupDialogue : public QWidget + { + Q_OBJECT + + public: + + StartupDialogue(); + + signals: + + void createDocument(); + + void loadDocument(); + }; +} + +#endif diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp new file mode 100644 index 000000000..affada012 --- /dev/null +++ b/apps/opencs/view/doc/subview.cpp @@ -0,0 +1,17 @@ +#include "subview.hpp" + +CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id) +{ + /// \todo add a button to the title bar that clones this sub view + + setWindowTitle (mUniversalId.toString().c_str()); + + /// \todo remove (for testing only) + setMinimumWidth (100); + setMinimumHeight (60); +} + +CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const +{ + return mUniversalId; +} diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp new file mode 100644 index 000000000..985c5eb3c --- /dev/null +++ b/apps/opencs/view/doc/subview.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_DOC_SUBVIEW_H +#define CSV_DOC_SUBVIEW_H + +#include "../../model/doc/document.hpp" + +#include "../../model/world/universalid.hpp" + +#include "subviewfactory.hpp" + +#include + +class QUndoStack; + +namespace CSMWorld +{ + class Data; +} + +namespace CSVDoc +{ + class SubView : public QDockWidget + { + Q_OBJECT + + CSMWorld::UniversalId mUniversalId; + + // not implemented + SubView (const SubView&); + SubView& operator= (SubView&); + + public: + + SubView (const CSMWorld::UniversalId& id); + + CSMWorld::UniversalId getUniversalId() const; + + virtual void setEditLock (bool locked) = 0; + + signals: + + void focusId (const CSMWorld::UniversalId& universalId); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp new file mode 100644 index 000000000..8576f6b1d --- /dev/null +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -0,0 +1,38 @@ + +#include "subviewfactory.hpp" + +#include + +#include + +CSVDoc::SubViewFactoryBase::SubViewFactoryBase() {} + +CSVDoc::SubViewFactoryBase::~SubViewFactoryBase() {} + + +CSVDoc::SubViewFactoryManager::SubViewFactoryManager() {} + +CSVDoc::SubViewFactoryManager::~SubViewFactoryManager() +{ + for (std::map::iterator iter (mSubViewFactories.begin()); + iter!=mSubViewFactories.end(); ++iter) + delete iter->second; +} + +void CSVDoc::SubViewFactoryManager::add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory) +{ + assert (mSubViewFactories.find (id)==mSubViewFactories.end()); + + mSubViewFactories.insert (std::make_pair (id, factory)); +} + +CSVDoc::SubView *CSVDoc::SubViewFactoryManager::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) +{ + std::map::iterator iter = mSubViewFactories.find (id.getType()); + + if (iter==mSubViewFactories.end()) + throw std::runtime_error ("Failed to create a sub view for: " + id.toString()); + + return iter->second->makeSubView (id, document); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/subviewfactory.hpp b/apps/opencs/view/doc/subviewfactory.hpp new file mode 100644 index 000000000..1f7c15480 --- /dev/null +++ b/apps/opencs/view/doc/subviewfactory.hpp @@ -0,0 +1,55 @@ +#ifndef CSV_DOC_SUBVIEWFACTORY_H +#define CSV_DOC_SUBVIEWFACTORY_H + +#include + +#include "../../model/world/universalid.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVDoc +{ + class SubView; + + class SubViewFactoryBase + { + // not implemented + SubViewFactoryBase (const SubViewFactoryBase&); + SubViewFactoryBase& operator= (const SubViewFactoryBase&); + + public: + + SubViewFactoryBase(); + + virtual ~SubViewFactoryBase(); + + virtual SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0; + ///< The ownership of the returned sub view is not transferred. + }; + + class SubViewFactoryManager + { + std::map mSubViewFactories; + + // not implemented + SubViewFactoryManager (const SubViewFactoryManager&); + SubViewFactoryManager& operator= (const SubViewFactoryManager&); + + public: + + SubViewFactoryManager(); + + ~SubViewFactoryManager(); + + void add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory); + ///< The ownership of \a factory is transferred to this. + + SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + ///< The ownership of the returned sub view is not transferred. + }; +} + +#endif diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp new file mode 100644 index 000000000..d16e0b2b7 --- /dev/null +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -0,0 +1,50 @@ +#ifndef CSV_DOC_SUBVIEWFACTORYIMP_H +#define CSV_DOC_SUBVIEWFACTORYIMP_H + +#include "../../model/doc/document.hpp" + +#include "subviewfactory.hpp" + +namespace CSVDoc +{ + template + class SubViewFactory : public SubViewFactoryBase + { + public: + + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + }; + + template + CSVDoc::SubView *SubViewFactory::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) + { + return new SubViewT (id, document); + } + + template + class SubViewFactoryWithCreateFlag : public SubViewFactoryBase + { + bool mCreateAndDelete; + + public: + + SubViewFactoryWithCreateFlag (bool createAndDelete); + + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + }; + + template + SubViewFactoryWithCreateFlag::SubViewFactoryWithCreateFlag (bool createAndDelete) + : mCreateAndDelete (createAndDelete) + {} + + template + CSVDoc::SubView *SubViewFactoryWithCreateFlag::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) + { + return new SubViewT (id, document, mCreateAndDelete); + } +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp new file mode 100644 index 000000000..286d7a6ed --- /dev/null +++ b/apps/opencs/view/doc/view.cpp @@ -0,0 +1,245 @@ +#include "view.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../world/subviews.hpp" + +#include "../tools/subviews.hpp" + +#include "viewmanager.hpp" +#include "operations.hpp" +#include "subview.hpp" + +void CSVDoc::View::closeEvent (QCloseEvent *event) +{ + if (!mViewManager.closeRequest (this)) + event->ignore(); +} + +void CSVDoc::View::setupFileMenu() +{ + QMenu *file = menuBar()->addMenu (tr ("&File")); + + QAction *new_ = new QAction (tr ("New"), this); + connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest())); + file->addAction (new_); + + QAction *open = new QAction (tr ("&Open"), this); + connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); + file->addAction (open); + + mSave = new QAction (tr ("&Save"), this); + connect (mSave, SIGNAL (triggered()), this, SLOT (save())); + file->addAction (mSave); +} + +void CSVDoc::View::setupEditMenu() +{ + QMenu *edit = menuBar()->addMenu (tr ("&Edit")); + + mUndo = mDocument->getUndoStack().createUndoAction (this, tr("&Undo")); + mUndo->setShortcuts (QKeySequence::Undo); + edit->addAction (mUndo); + + mRedo= mDocument->getUndoStack().createRedoAction (this, tr("&Redo")); + mRedo->setShortcuts (QKeySequence::Redo); + edit->addAction (mRedo); +} + +void CSVDoc::View::setupViewMenu() +{ + QMenu *view = menuBar()->addMenu (tr ("&View")); + + QAction *newWindow = new QAction (tr ("&New View"), this); + connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); + view->addAction (newWindow); +} + +void CSVDoc::View::setupWorldMenu() +{ + QMenu *world = menuBar()->addMenu (tr ("&World")); + + QAction *globals = new QAction (tr ("Globals"), this); + connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); + world->addAction (globals); + + QAction *gmsts = new QAction (tr ("Game settings"), this); + connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); + world->addAction (gmsts); + + mVerify = new QAction (tr ("&Verify"), this); + connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); + world->addAction (mVerify); +} + +void CSVDoc::View::setupUi() +{ + setupFileMenu(); + setupEditMenu(); + setupViewMenu(); + setupWorldMenu(); +} + +void CSVDoc::View::updateTitle() +{ + std::ostringstream stream; + + stream << mDocument->getName(); + + if (mDocument->getState() & CSMDoc::State_Modified) + stream << " *"; + + if (mViewTotal>1) + stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; + + setWindowTitle (stream.str().c_str()); +} + +void CSVDoc::View::updateActions() +{ + bool editing = !(mDocument->getState() & CSMDoc::State_Locked); + + for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) + (*iter)->setEnabled (editing); + + mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); + mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); + + mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving)); + mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); +} + +CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews, QMainWindow *viewParent) + : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews), QMainWindow (viewParent) +{ + setDockOptions (QMainWindow::AllowNestedDocks); + + resize (300, 300); /// \todo get default size from settings and set reasonable minimal size + + mSubViewWindow = new QMainWindow(); + setCentralWidget (mSubViewWindow); + + mOperations = new Operations; + addDockWidget (Qt::BottomDockWidgetArea, mOperations); + + updateTitle(); + + setupUi(); + + CSVWorld::addSubViewFactories (mSubViewFactory); + CSVTools::addSubViewFactories (mSubViewFactory); + + connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); +} + +CSVDoc::View::~View() +{ +} + +const CSMDoc::Document *CSVDoc::View::getDocument() const +{ + return mDocument; +} + +CSMDoc::Document *CSVDoc::View::getDocument() +{ + return mDocument; +} + +void CSVDoc::View::setIndex (int viewIndex, int totalViews) +{ + mViewIndex = viewIndex; + mViewTotal = totalViews; + updateTitle(); +} + +void CSVDoc::View::updateDocumentState() +{ + updateTitle(); + updateActions(); + + static const int operations[] = + { + CSMDoc::State_Saving, CSMDoc::State_Verifying, + -1 // end marker + }; + + int state = mDocument->getState() ; + + for (int i=0; operations[i]!=-1; ++i) + if (!(state & operations[i])) + mOperations->quitOperation (operations[i]); + + QList subViews = findChildren(); + + for (QList::iterator iter (subViews.begin()); iter!=subViews.end(); ++iter) + (*iter)->setEditLock (state & CSMDoc::State_Locked); +} + +void CSVDoc::View::updateProgress (int current, int max, int type, int threads) +{ + mOperations->setProgress (current, max, type, threads); +} + +void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) +{ + /// \todo add an user setting for limiting the number of sub views per top level view. Automatically open a new top level view if this + /// number is exceeded + + /// \todo if the sub view limit setting is one, the sub view title bar should be hidden and the text in the main title bar adjusted + /// accordingly + + /// \todo add an user setting to reuse sub views (on a per document basis or on a per top level view basis) + + SubView *view = mSubViewFactory.makeSubView (id, *mDocument); + mSubViewWindow->addDockWidget (Qt::TopDockWidgetArea, view); + + connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this, + SLOT (addSubView (const CSMWorld::UniversalId&))); + + view->show(); +} + +void CSVDoc::View::newView() +{ + mViewManager.addView (mDocument); +} + +void CSVDoc::View::save() +{ + mDocument->save(); +} + +void CSVDoc::View::verify() +{ + addSubView (mDocument->verify()); +} + +void CSVDoc::View::addGlobalsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Globals); +} + +void CSVDoc::View::addGmstsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Gmsts); +} + +void CSVDoc::View::abortOperation (int type) +{ + mDocument->abortOperation (type); + updateActions(); +} + +QDockWidget *CSVDoc::View::getOperations() const +{ + return mOperations; +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp new file mode 100644 index 000000000..28ab24b74 --- /dev/null +++ b/apps/opencs/view/doc/view.hpp @@ -0,0 +1,113 @@ +#ifndef CSV_DOC_VIEW_H +#define CSV_DOC_VIEW_H + +#include +#include + +#include + +#include "subviewfactory.hpp" + +class QAction; +class QDockWidget; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} + +namespace CSVDoc +{ + class ViewManager; + class Operations; + + class View : public QMainWindow + { + Q_OBJECT + + ViewManager& mViewManager; + CSMDoc::Document *mDocument; + int mViewIndex; + int mViewTotal; + QAction *mUndo; + QAction *mRedo; + QAction *mSave; + QAction *mVerify; + std::vector mEditingActions; + Operations *mOperations; + SubViewFactoryManager mSubViewFactory; + QMainWindow* mSubViewWindow; + + // not implemented + View (const View&); + View& operator= (const View&); + + private: + + void closeEvent (QCloseEvent *event); + + void setupFileMenu(); + + void setupEditMenu(); + + void setupViewMenu(); + + void setupWorldMenu(); + + void setupUi(); + + void updateTitle(); + + void updateActions(); + + public: + + View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews, QMainWindow *viewParent); + ///< The ownership of \a document is not transferred to *this. + + virtual ~View(); + + const CSMDoc::Document *getDocument() const; + + CSMDoc::Document *getDocument(); + + void setIndex (int viewIndex, int totalViews); + + void updateDocumentState(); + + void updateProgress (int current, int max, int type, int threads); + + QDockWidget *getOperations() const; + + signals: + + void newDocumentRequest(); + + void loadDocumentRequest(); + + public slots: + + void addSubView (const CSMWorld::UniversalId& id); + + private slots: + + void newView(); + + void save(); + + void verify(); + + void addGlobalsSubView(); + + void addGmstsSubView(); + + void abortOperation (int type); + }; +} + +#endif diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp new file mode 100644 index 000000000..718b80728 --- /dev/null +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -0,0 +1,128 @@ + +#include "viewmanager.hpp" + +#include + +#include "../../model/doc/documentmanager.hpp" +#include "../../model/doc/document.hpp" + +#include "../world/util.hpp" +#include "../world/enumdelegate.hpp" +#include "../world/vartypedelegate.hpp" + +#include "view.hpp" + +void CSVDoc::ViewManager::updateIndices() +{ + std::map > documents; + + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + { + std::map >::iterator document = documents.find ((*iter)->getDocument()); + + if (document==documents.end()) + document = + documents.insert ( + std::make_pair ((*iter)->getDocument(), std::make_pair (0, countViews ((*iter)->getDocument())))). + first; + + (*iter)->setIndex (document->second.first++, document->second.second); + } +} + +CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) +: mDocumentManager (documentManager) +{ + mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection; + + mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType, + new CSVWorld::VarTypeDelegateFactory (ESM::VT_None, ESM::VT_String, ESM::VT_Int, ESM::VT_Float)); + + mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType, + new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); + +} + +CSVDoc::ViewManager::~ViewManager() +{ + delete mDelegateFactories; + + for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + delete *iter; +} + +CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) +{ + if (countViews (document)==0) + { + // new document + connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), + this, SLOT (documentStateChanged (int, CSMDoc::Document *))); + + connect (document, SIGNAL (progress (int, int, int, int, CSMDoc::Document *)), + this, SLOT (progress (int, int, int, int, CSMDoc::Document *))); + } + + QMainWindow *mainWindow = new QMainWindow; + + View *view = new View (*this, document, countViews (document)+1, mainWindow); + + mViews.push_back (view); + + view->show(); + + connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest())); + connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); + + updateIndices(); + + return view; +} + +int CSVDoc::ViewManager::countViews (const CSMDoc::Document *document) const +{ + int count = 0; + + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + ++count; + + return count; +} + +bool CSVDoc::ViewManager::closeRequest (View *view) +{ + std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); + + if (iter!=mViews.end()) + { + bool last = countViews (view->getDocument())<=1; + + /// \todo check if save is in progress -> warn user about possible data loss + /// \todo check if document has not been saved -> return false and start close dialogue + + mViews.erase (iter); + view->deleteLater(); + + if (last) + mDocumentManager.removeDocument (view->getDocument()); + else + updateIndices(); + } + + return true; +} + +void CSVDoc::ViewManager::documentStateChanged (int state, CSMDoc::Document *document) +{ + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + (*iter)->updateDocumentState(); +} + +void CSVDoc::ViewManager::progress (int current, int max, int type, int threads, CSMDoc::Document *document) +{ + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + (*iter)->updateProgress (current, max, type, threads); +} diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp new file mode 100644 index 000000000..72e7a3e1a --- /dev/null +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -0,0 +1,66 @@ +#ifndef CSV_DOC_VIEWMANAGER_H +#define CSV_DOC_VIEWMANAGER_H + +#include + +#include + +namespace CSMDoc +{ + class Document; + class DocumentManager; +} + +namespace CSVWorld +{ + class CommandDelegateFactoryCollection; +} + +namespace CSVDoc +{ + class View; + + class ViewManager : public QObject + { + Q_OBJECT + + CSMDoc::DocumentManager& mDocumentManager; + std::vector mViews; + CSVWorld::CommandDelegateFactoryCollection *mDelegateFactories; + + // not implemented + ViewManager (const ViewManager&); + ViewManager& operator= (const ViewManager&); + + void updateIndices(); + + public: + + ViewManager (CSMDoc::DocumentManager& documentManager); + + virtual ~ViewManager(); + + View *addView (CSMDoc::Document *document); + ///< The ownership of the returned view is not transferred. + + int countViews (const CSMDoc::Document *document) const; + ///< Return number of views for \a document. + + bool closeRequest (View *view); + + signals: + + void newDocumentRequest(); + + void loadDocumentRequest(); + + private slots: + + void documentStateChanged (int state, CSMDoc::Document *document); + + void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + }; + +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp new file mode 100644 index 000000000..fe1be85d7 --- /dev/null +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -0,0 +1,32 @@ + +#include "reportsubview.hpp" + +#include +#include + +#include "../../model/tools/reportmodel.hpp" + +CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) +: CSVDoc::SubView (id), mModel (document.getReport (id)) +{ + setWidget (mTable = new QTableView (this)); + mTable->setModel (mModel); + + mTable->horizontalHeader()->setResizeMode (QHeaderView::Interactive); + mTable->verticalHeader()->hide(); + mTable->setSortingEnabled (true); + mTable->setSelectionBehavior (QAbstractItemView::SelectRows); + mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); + + connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); +} + +void CSVTools::ReportSubView::setEditLock (bool locked) +{ + // ignored. We don't change document state anyway. +} + +void CSVTools::ReportSubView::show (const QModelIndex& index) +{ + focusId (mModel->getUniversalId (index.row())); +} \ No newline at end of file diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp new file mode 100644 index 000000000..626ceb663 --- /dev/null +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -0,0 +1,42 @@ +#ifndef CSV_TOOLS_REPORTSUBVIEW_H +#define CSV_TOOLS_REPORTSUBVIEW_H + +#include "../doc/subview.hpp" + +class QTableView; +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class ReportModel; +} + +namespace CSVTools +{ + class Table; + + class ReportSubView : public CSVDoc::SubView + { + Q_OBJECT + + CSMTools::ReportModel *mModel; + QTableView *mTable; + + public: + + ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + + virtual void setEditLock (bool locked); + + private slots: + + void show (const QModelIndex& index); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp new file mode 100644 index 000000000..781cf602e --- /dev/null +++ b/apps/opencs/view/tools/subviews.cpp @@ -0,0 +1,12 @@ + +#include "subviews.hpp" + +#include "../doc/subviewfactoryimp.hpp" + +#include "reportsubview.hpp" + +void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +{ + manager.add (CSMWorld::UniversalId::Type_VerificationResults, + new CSVDoc::SubViewFactory); +} \ No newline at end of file diff --git a/apps/opencs/view/tools/subviews.hpp b/apps/opencs/view/tools/subviews.hpp new file mode 100644 index 000000000..1bac32228 --- /dev/null +++ b/apps/opencs/view/tools/subviews.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_TOOLS_SUBVIEWS_H +#define CSV_TOOLS_SUBVIEWS_H + +namespace CSVDoc +{ + class SubViewFactoryManager; +} + +namespace CSVTools +{ + void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); +} + +#endif diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp new file mode 100644 index 000000000..e16de99ef --- /dev/null +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -0,0 +1,98 @@ + +#include "dialoguesubview.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/world/columnbase.hpp" +#include "../../model/world/idtable.hpp" + +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + bool createAndDelete) +: SubView (id) +{ + QWidget *widget = new QWidget (this); + + setWidget (widget); + + QGridLayout *layout = new QGridLayout; + + widget->setLayout (layout); + + QAbstractTableModel *model = document.getData().getTableModel (id); + + int columns = model->columnCount(); + + mWidgetMapper = new QDataWidgetMapper (this); + mWidgetMapper->setModel (model); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Dialogue) + { + layout->addWidget (new QLabel (model->headerData (i, Qt::Horizontal).toString()), i, 0); + + CSMWorld::ColumnBase::Display display = static_cast + (model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + QWidget *widget = 0; + + if (model->flags (model->index (0, i)) & Qt::ItemIsEditable) + { + switch (display) + { + case CSMWorld::ColumnBase::Display_String: + + layout->addWidget (widget = new QLineEdit, i, 1); + break; + + case CSMWorld::ColumnBase::Display_Integer: + + /// \todo configure widget properly (range) + layout->addWidget (widget = new QSpinBox, i, 1); + break; + + case CSMWorld::ColumnBase::Display_Float: + + /// \todo configure widget properly (range, format?) + layout->addWidget (widget = new QDoubleSpinBox, i, 1); + break; + + default: break; // silence warnings for other times for now + } + } + else + { + switch (display) + { + case CSMWorld::ColumnBase::Display_String: + case CSMWorld::ColumnBase::Display_Integer: + case CSMWorld::ColumnBase::Display_Float: + + layout->addWidget (widget = new QLabel, i, 1); + break; + + default: break; // silence warnings for other times for now + } + } + + if (widget) + mWidgetMapper->addMapping (widget, i); + } + } + + mWidgetMapper->setCurrentModelIndex ( + dynamic_cast (*model).getModelIndex (id.getId(), 0)); +} + +void CSVWorld::DialogueSubView::setEditLock (bool locked) +{ + +} \ No newline at end of file diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp new file mode 100644 index 000000000..64715f5b7 --- /dev/null +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -0,0 +1,27 @@ +#ifndef CSV_WORLD_DIALOGUESUBVIEW_H +#define CSV_WORLD_DIALOGUESUBVIEW_H + +#include "../doc/subview.hpp" + +class QDataWidgetMapper; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class DialogueSubView : public CSVDoc::SubView + { + QDataWidgetMapper *mWidgetMapper; + + public: + + DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + + virtual void setEditLock (bool locked); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp new file mode 100644 index 000000000..7a8b45373 --- /dev/null +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -0,0 +1,101 @@ + +#include "enumdelegate.hpp" + +#include + +#include +#include +#include + +#include "../../model/world/commands.hpp" + +void CSVWorld::EnumDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const +{ + if (QComboBox *comboBox = dynamic_cast (editor)) + { + QString value = comboBox->currentText(); + + for (std::vector >::const_iterator iter (mValues.begin()); + iter!=mValues.end(); ++iter) + if (iter->second==value) + { + addCommands (model, index, iter->first); + break; + } + } +} + +void CSVWorld::EnumDelegate::addCommands (QAbstractItemModel *model, + const QModelIndex& index, int type) const +{ + getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); +} + + +CSVWorld::EnumDelegate::EnumDelegate (const std::vector >& values, + QUndoStack& undoStack, QObject *parent) +: CommandDelegate (undoStack, parent), mValues (values) +{ + +} + +QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + QComboBox *comboBox = new QComboBox (parent); + + for (std::vector >::const_iterator iter (mValues.begin()); + iter!=mValues.end(); ++iter) + comboBox->addItem (iter->second); + + return comboBox; +} + +void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index) const +{ + if (QComboBox *comboBox = dynamic_cast (editor)) + { + int value = index.data (Qt::EditRole).toInt(); + + std::size_t size = mValues.size(); + + for (std::size_t i=0; isetCurrentIndex (i); + break; + } + } +} + +void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + QStyleOptionViewItemV4 option2 (option); + + int value = index.data().toInt(); + + for (std::vector >::const_iterator iter (mValues.begin()); + iter!=mValues.end(); ++iter) + if (iter->first==value) + { + option2.text = iter->second; + + QApplication::style()->drawControl (QStyle::CE_ItemViewItem, &option2, painter); + + break; + } +} + + +CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate (QUndoStack& undoStack, + QObject *parent) const +{ + return new EnumDelegate (mValues, undoStack, parent); +} + +void CSVWorld::EnumDelegateFactory::add (int value, const QString& name) +{ + mValues.push_back (std::make_pair (value, name)); +} diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp new file mode 100644 index 000000000..f11252371 --- /dev/null +++ b/apps/opencs/view/world/enumdelegate.hpp @@ -0,0 +1,57 @@ +#ifndef CSV_WORLD_ENUMDELEGATE_H +#define CSV_WORLD_ENUMDELEGATE_H + +#include + +#include + +#include + +#include "util.hpp" + +namespace CSVWorld +{ + /// \brief Integer value that represents an enum and is interacted with via a combobox + class EnumDelegate : public CommandDelegate + { + std::vector > mValues; + + private: + + virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const; + + virtual void addCommands (QAbstractItemModel *model, + const QModelIndex& index, int type) const; + + public: + + EnumDelegate (const std::vector >& values, + QUndoStack& undoStack, QObject *parent); + + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, + const QModelIndex& index) const; + + virtual void setEditorData (QWidget *editor, const QModelIndex& index) const; + + virtual void paint (QPainter *painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const; + + }; + + class EnumDelegateFactory : public CommandDelegateFactory + { + std::vector > mValues; + + public: + + virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + + void add (int value, const QString& name); + }; + + +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp new file mode 100644 index 000000000..351007ded --- /dev/null +++ b/apps/opencs/view/world/subviews.cpp @@ -0,0 +1,19 @@ + +#include "subviews.hpp" + +#include "../doc/subviewfactoryimp.hpp" + +#include "tablesubview.hpp" +#include "dialoguesubview.hpp" + +void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +{ + manager.add (CSMWorld::UniversalId::Type_Globals, + new CSVDoc::SubViewFactoryWithCreateFlag (true)); + + manager.add (CSMWorld::UniversalId::Type_Gmsts, + new CSVDoc::SubViewFactoryWithCreateFlag (false)); + + manager.add (CSMWorld::UniversalId::Type_Global, + new CSVDoc::SubViewFactoryWithCreateFlag (true)); +} \ No newline at end of file diff --git a/apps/opencs/view/world/subviews.hpp b/apps/opencs/view/world/subviews.hpp new file mode 100644 index 000000000..51e4cb083 --- /dev/null +++ b/apps/opencs/view/world/subviews.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_WORLD_SUBVIEWS_H +#define CSV_WORLD_SUBVIEWS_H + +namespace CSVDoc +{ + class SubViewFactoryManager; +} + +namespace CSVWorld +{ + void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); +} + +#endif diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp new file mode 100644 index 000000000..f9167d259 --- /dev/null +++ b/apps/opencs/view/world/table.cpp @@ -0,0 +1,204 @@ + +#include "table.hpp" + +#include + +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtableproxymodel.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" + +#include "util.hpp" + +void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + QMenu menu (this); + + /// \todo add menu items for select all and clear selection + + if (!mEditLock) + { + if (mCreateAction) + menu.addAction (mCreateAction); + + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); + + if (listDeletableSelectedIds().size()>0) + menu.addAction (mDeleteAction); + } + + menu.exec (event->globalPos()); +} + +std::vector CSVWorld::Table::listRevertableSelectedIds() const +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::vector revertableIds; + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + { + std::string id = mProxyModel->data (*iter).toString().toStdString(); + + CSMWorld::RecordBase::State state = + static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + revertableIds.push_back (id); + } + + return revertableIds; +} + +std::vector CSVWorld::Table::listDeletableSelectedIds() const +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::vector deletableIds; + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + { + std::string id = mProxyModel->data (*iter).toString().toStdString(); + + CSMWorld::RecordBase::State state = + static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_Deleted) + deletableIds.push_back (id); + } + + return deletableIds; +} + +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, + bool createAndDelete) +: mUndoStack (undoStack), mCreateAction (0), mEditLock (false) +{ + mModel = &dynamic_cast (*data.getTableModel (id)); + + mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); + horizontalHeader()->setResizeMode (QHeaderView::Interactive); + verticalHeader()->hide(); + setSortingEnabled (true); + setSelectionBehavior (QAbstractItemView::SelectRows); + setSelectionMode (QAbstractItemView::ExtendedSelection); + + int columns = mModel->columnCount(); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Table) + { + CSMWorld::ColumnBase::Display display = static_cast ( + mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, + undoStack, this); + + mDelegates.push_back (delegate); + setItemDelegateForColumn (i, delegate); + } + else + hideColumn (i); + } + + /// \todo make initial layout fill the whole width of the table + + if (createAndDelete) + { + mCreateAction = new QAction (tr ("Add Record"), this); + connect (mCreateAction, SIGNAL (triggered()), this, SLOT (createRecord())); + addAction (mCreateAction); + } + + mRevertAction = new QAction (tr ("Revert Record"), this); + connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + addAction (mRevertAction); + + mDeleteAction = new QAction (tr ("Delete Record"), this); + connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + addAction (mDeleteAction); +} + +void CSVWorld::Table::setEditLock (bool locked) +{ + for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) + (*iter)->setEditLock (locked); + + mEditLock = locked; +} + +CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const +{ + return CSMWorld::UniversalId ( + static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), + mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); +} + +#include /// \todo remove +void CSVWorld::Table::createRecord() +{ + if (!mEditLock) + { + /// \todo ask the user for an ID instead. + static int index = 0; + + std::ostringstream stream; + stream << "id" << index++; + + mUndoStack.push (new CSMWorld::CreateCommand (*mProxyModel, stream.str())); + } +} + +void CSVWorld::Table::revertRecord() +{ + if (!mEditLock) + { + std::vector revertableIds = listRevertableSelectedIds(); + + if (revertableIds.size()>0) + { + if (revertableIds.size()>1) + mUndoStack.beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + + if (revertableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::deleteRecord() +{ + if (!mEditLock) + { + std::vector deletableIds = listDeletableSelectedIds(); + + if (deletableIds.size()>0) + { + if (deletableIds.size()>1) + mUndoStack.beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + + if (deletableIds.size()>1) + mUndoStack.endMacro(); + } + } +} \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp new file mode 100644 index 000000000..df0224583 --- /dev/null +++ b/apps/opencs/view/world/table.hpp @@ -0,0 +1,65 @@ +#ifndef CSV_WORLD_TABLE_H +#define CSV_WORLD_TABLE_H + +#include +#include + +#include + +class QUndoStack; +class QAction; + +namespace CSMWorld +{ + class Data; + class UniversalId; + class IdTableProxyModel; + class IdTable; +} + +namespace CSVWorld +{ + class CommandDelegate; + + ///< Table widget + class Table : public QTableView + { + Q_OBJECT + + std::vector mDelegates; + QUndoStack& mUndoStack; + QAction *mCreateAction; + QAction *mRevertAction; + QAction *mDeleteAction; + CSMWorld::IdTableProxyModel *mProxyModel; + CSMWorld::IdTable *mModel; + bool mEditLock; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + std::vector listRevertableSelectedIds() const; + + std::vector listDeletableSelectedIds() const; + + public: + + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete); + ///< \param createAndDelete Allow creation and deletion of records. + + void setEditLock (bool locked); + + CSMWorld::UniversalId getUniversalId (int row) const; + + private slots: + + void createRecord(); + + void revertRecord(); + + void deleteRecord(); + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp new file mode 100644 index 000000000..f4deceb49 --- /dev/null +++ b/apps/opencs/view/world/tablesubview.cpp @@ -0,0 +1,26 @@ + +#include "tablesubview.hpp" + +#include "../../model/doc/document.hpp" + +#include "table.hpp" + +CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + bool createAndDelete) +: SubView (id) +{ + setWidget (mTable = new Table (id, document.getData(), document.getUndoStack(), createAndDelete)); + + connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (rowActivated (const QModelIndex&))); +} + +void CSVWorld::TableSubView::setEditLock (bool locked) +{ + mTable->setEditLock (locked); +} + +void CSVWorld::TableSubView::rowActivated (const QModelIndex& index) +{ + /// \todo re-enable, after dialogue sub views have been fixed up +// focusId (mTable->getUniversalId (index.row())); +} \ No newline at end of file diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp new file mode 100644 index 000000000..0e7b8aa30 --- /dev/null +++ b/apps/opencs/view/world/tablesubview.hpp @@ -0,0 +1,35 @@ +#ifndef CSV_WORLD_TABLESUBVIEW_H +#define CSV_WORLD_TABLESUBVIEW_H + +#include "../doc/subview.hpp" + +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class Table; + + class TableSubView : public CSVDoc::SubView + { + Q_OBJECT + + Table *mTable; + + public: + + TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + + virtual void setEditLock (bool locked); + + private slots: + + void rowActivated (const QModelIndex& index); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp new file mode 100644 index 000000000..5ada1d84f --- /dev/null +++ b/apps/opencs/view/world/util.cpp @@ -0,0 +1,127 @@ + +#include "util.hpp" + +#include + +#include + +#include "../../model/world/commands.hpp" + +CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) +: mModel (model) +{} + +int CSVWorld::NastyTableModelHack::rowCount (const QModelIndex & parent) const +{ + return mModel.rowCount (parent); +} + +int CSVWorld::NastyTableModelHack::columnCount (const QModelIndex & parent) const +{ + return mModel.columnCount (parent); +} + +QVariant CSVWorld::NastyTableModelHack::data (const QModelIndex & index, int role) const +{ + return mModel.data (index, role); +} + +bool CSVWorld::NastyTableModelHack::setData ( const QModelIndex &index, const QVariant &value, int role) +{ + mData = value; + return true; +} + +QVariant CSVWorld::NastyTableModelHack::getData() const +{ + return mData; +} + + +CSVWorld::CommandDelegateFactory::~CommandDelegateFactory() {} + + +CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = 0; + +CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection() +{ + if (sThis) + throw std::logic_error ("multiple instances of CSVWorld::CommandDelegateFactoryCollection"); + + sThis = this; +} + +CSVWorld::CommandDelegateFactoryCollection::~CommandDelegateFactoryCollection() +{ + sThis = 0; + + for (std::map::iterator iter ( + mFactories.begin()); + iter!=mFactories.end(); ++iter) + delete iter->second; +} + +void CSVWorld::CommandDelegateFactoryCollection::add (CSMWorld::ColumnBase::Display display, + CommandDelegateFactory *factory) +{ + mFactories.insert (std::make_pair (display, factory)); +} + +CSVWorld::CommandDelegate *CSVWorld::CommandDelegateFactoryCollection::makeDelegate ( + CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, QObject *parent) const +{ + std::map::const_iterator iter = + mFactories.find (display); + + if (iter!=mFactories.end()) + return iter->second->makeDelegate (undoStack, parent); + + return new CommandDelegate (undoStack, parent); +} + +const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFactoryCollection::get() +{ + if (!sThis) + throw std::logic_error ("no instance of CSVWorld::CommandDelegateFactoryCollection"); + + return *sThis; +} + + +QUndoStack& CSVWorld::CommandDelegate::getUndoStack() const +{ + return mUndoStack; +} + +void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const +{ + NastyTableModelHack hack (*model); + QStyledItemDelegate::setModelData (editor, &hack, index); + mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData())); +} + +CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent) +: QStyledItemDelegate (parent), mUndoStack (undoStack), mEditLock (false) +{} + +void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const +{ + if (!mEditLock) + { + setModelDataImp (editor, model, index); + } + + ///< \todo provide some kind of feedback to the user, indicating that editing is currently not possible. +} + +void CSVWorld::CommandDelegate::setEditLock (bool locked) +{ + mEditLock = locked; +} + +bool CSVWorld::CommandDelegate::isEditLocked() const +{ + return mEditLock; +} \ No newline at end of file diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp new file mode 100644 index 000000000..5334abf9c --- /dev/null +++ b/apps/opencs/view/world/util.hpp @@ -0,0 +1,108 @@ +#ifndef CSV_WORLD_UTIL_H +#define CSV_WORLD_UTIL_H + +#include + +#include +#include + +#include "../../model/world/columnbase.hpp" + +class QUndoStack; + +namespace CSVWorld +{ + ///< \brief Getting the data out of an editor widget + /// + /// Really, Qt? Really? + class NastyTableModelHack : public QAbstractTableModel + { + QAbstractItemModel& mModel; + QVariant mData; + + public: + + NastyTableModelHack (QAbstractItemModel& model); + + int rowCount (const QModelIndex & parent = QModelIndex()) const; + + int columnCount (const QModelIndex & parent = QModelIndex()) const; + + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant getData() const; + }; + + class CommandDelegate; + + class CommandDelegateFactory + { + public: + + virtual ~CommandDelegateFactory(); + + virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const = 0; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + }; + + class CommandDelegateFactoryCollection + { + static CommandDelegateFactoryCollection *sThis; + std::map mFactories; + + private: + + // not implemented + CommandDelegateFactoryCollection (const CommandDelegateFactoryCollection&); + CommandDelegateFactoryCollection& operator= (const CommandDelegateFactoryCollection&); + + public: + + CommandDelegateFactoryCollection(); + + ~CommandDelegateFactoryCollection(); + + void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory); + ///< The ownership of \æ factory is transferred to *this. + /// + /// This function must not be called more than once per value of \æ display. + + CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, + QObject *parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + /// + /// If no factory is registered for \a display, a CommandDelegate will be returned. + + static const CommandDelegateFactoryCollection& get(); + + }; + + ///< \brief Use commands instead of manipulating the model directly + class CommandDelegate : public QStyledItemDelegate + { + QUndoStack& mUndoStack; + bool mEditLock; + + protected: + + QUndoStack& getUndoStack() const; + + virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const; + + public: + + CommandDelegate (QUndoStack& undoStack, QObject *parent); + + virtual void setModelData (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const; + + void setEditLock (bool locked); + + bool isEditLocked() const; + }; +} + +#endif diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp new file mode 100644 index 000000000..72cbaae42 --- /dev/null +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -0,0 +1,103 @@ + +#include "vartypedelegate.hpp" + +#include + +#include "../../model/world/commands.hpp" + +void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) + const +{ + QModelIndex next = model->index (index.row(), index.column()+1); + + QVariant old = model->data (next); + + QVariant value; + + switch (type) + { + case ESM::VT_Short: + case ESM::VT_Int: + case ESM::VT_Long: + + value = old.toInt(); + break; + + case ESM::VT_Float: + + value = old.toFloat(); + break; + + case ESM::VT_String: + + value = old.toString(); + break; + + default: break; // ignore the rest + } + + getUndoStack().beginMacro ( + "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + + getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); + getUndoStack().push (new CSMWorld::ModifyCommand (*model, next, value)); + + getUndoStack().endMacro(); +} + +CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, + QUndoStack& undoStack, QObject *parent) +: EnumDelegate (values, undoStack, parent) +{} + + +CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory (ESM::VarType type0, + ESM::VarType type1, ESM::VarType type2, ESM::VarType type3) +{ + if (type0!=ESM::VT_Unknown) + add (type0); + + if (type1!=ESM::VT_Unknown) + add (type1); + + if (type2!=ESM::VT_Unknown) + add (type2); + + if (type3!=ESM::VT_Unknown) + add (type3); +} + +CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate (QUndoStack& undoStack, + QObject *parent) const +{ + return new VarTypeDelegate (mValues, undoStack, parent); +} + +void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) +{ + struct Name + { + ESM::VarType mType; + const char *mName; + }; + + static const Name sNames[] = + { + { ESM::VT_None, "empty" }, + { ESM::VT_Short, "short" }, + { ESM::VT_Int, "integer" }, + { ESM::VT_Long, "long" }, + { ESM::VT_Float, "float" }, + { ESM::VT_String, "string" }, + { ESM::VT_Unknown, 0 } // end marker + }; + + for (int i=0; sNames[i].mName; ++i) + if (sNames[i].mType==type) + { + mValues.push_back (std::make_pair (type, sNames[i].mName)); + return; + } + + throw std::logic_error ("Unsupported variable type"); +} diff --git a/apps/opencs/view/world/vartypedelegate.hpp b/apps/opencs/view/world/vartypedelegate.hpp new file mode 100644 index 000000000..c8493f029 --- /dev/null +++ b/apps/opencs/view/world/vartypedelegate.hpp @@ -0,0 +1,40 @@ +#ifndef CSV_WORLD_VARTYPEDELEGATE_H +#define CSV_WORLD_VARTYPEDELEGATE_H + +#include + +#include "enumdelegate.hpp" + +namespace CSVWorld +{ + class VarTypeDelegate : public EnumDelegate + { + private: + + virtual void addCommands (QAbstractItemModel *model, + const QModelIndex& index, int type) const; + + public: + + VarTypeDelegate (const std::vector >& values, + QUndoStack& undoStack, QObject *parent); + }; + + class VarTypeDelegateFactory : public CommandDelegateFactory + { + std::vector > mValues; + + public: + + VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown, + ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown, + ESM::VarType type3 = ESM::VT_Unknown); + + virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + + void add (ESM::VarType type); + }; +} + +#endif diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e2a2e7f14..8df2136e5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -14,9 +14,9 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap occlusionquery terrain terrainmaterial water shadows - compositors characterpreview externalrendering globalmap + renderingmanager debugging sky player animation npcanimation creatureanimation activatoranimation + actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows + compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction ) add_openmw_dir (mwinput @@ -26,11 +26,11 @@ add_openmw_dir (mwinput add_openmw_dir (mwgui text_input widgets race class birth review windowmanagerimp console dialogue dialogue_history window_base stats_window messagebox journalwindow charactercreation - map_window window_pinnable_base cursorreplace tooltips scrollwindow bookwindow list + map_window window_pinnable_base tooltips scrollwindow bookwindow list formatting inventorywindow container hud countdialog tradewindow settingswindow confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog - enchantingdialog trainingwindow travelwindow + enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons ) add_openmw_dir (mwdialogue @@ -41,7 +41,7 @@ add_openmw_dir (mwscript locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions guiextensions soundextensions skyextensions statsextensions containerextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions - animationextensions transformationextensions consoleextensions userextensions + animationextensions transformationextensions consoleextensions userextensions locals ) add_openmw_dir (mwsound @@ -62,8 +62,9 @@ add_openmw_dir (mwclass ) add_openmw_dir (mwmechanics - mechanicsmanagerimp stat creaturestats magiceffects movement actors drawstate spells - activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow aiescort aiactivate + mechanicsmanagerimp stat character creaturestats magiceffects movement actors activators + drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow + aiescort aiactivate ) add_openmw_dir (mwbase @@ -72,15 +73,20 @@ add_openmw_dir (mwbase ) # Main executable + IF(OGRE_STATIC) +ADD_DEFINITIONS(-DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL) +set(OGRE_STATIC_PLUGINS ${OGRE_Plugin_OctreeSceneManager_LIBRARIES} ${OGRE_Plugin_ParticleFX_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES}) IF(WIN32) -ADD_DEFINITIONS(-DENABLE_PLUGIN_CgProgramManager -DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_-DENABLE_PLUGIN_Direct3D9 -DENABLE_PLUGIN_GL) -set(OGRE_STATIC_PLUGINS ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${OGRE_Plugin_OctreeSceneManager_LIBRARIES} ${OGRE_Plugin_ParticleFX_LIBRARIES} ${OGRE_RenderSystem_Direct3D9_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES}) -ELSE(WIN32) -ADD_DEFINITIONS(-DENABLE_PLUGIN_CgProgramManager -DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL) -set(OGRE_STATIC_PLUGINS ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${Cg_LIBRARIES} ${OGRE_Plugin_OctreeSceneManager_LIBRARIES} ${OGRE_Plugin_ParticleFX_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES}) +ADD_DEFINITIONS(-DENABLE_PLUGIN_Direct3D9) +list (APPEND OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_Direct3D9_LIBRARIES}) ENDIF(WIN32) +IF (Cg_FOUND) +ADD_DEFINITIONS(-DENABLE_PLUGIN_CgProgramManager) +list (APPEND OGRE_STATIC_PLUGINS ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${Cg_LIBRARIES}) +ENDIF (Cg_FOUND) ENDIF(OGRE_STATIC) + add_executable(openmw ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} ${OPENMW_FILES} @@ -120,6 +126,12 @@ if(APPLE) find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) target_link_libraries(openmw ${CARBON_FRAMEWORK} ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) + + if (FFMPEG_FOUND) + find_library(COREVIDEO_FRAMEWORK CoreVideo) + find_library(VDA_FRAMEWORK VideoDecodeAcceleration) + target_link_libraries(openmw ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK}) + endif() endif(APPLE) if(DPKG_PROGRAM) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2299053cd..62e106f3f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -8,15 +8,16 @@ #include #include +#include +#include #include -#include -#include +#include +#include #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" -#include "mwgui/cursorreplace.hpp" #include "mwscript/scriptmanagerimp.hpp" #include "mwscript/extensions.hpp" @@ -61,18 +62,26 @@ void OMW::Engine::setAnimationVerbose(bool animverbose) { } +bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) +{ + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) + MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame); + return true; +} + bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) { try { - mEnvironment.setFrameDuration (evt.timeSinceLastFrame); + float frametime = std::min(evt.timeSinceLastFrame, 0.2f); + mEnvironment.setFrameDuration(frametime); // update input - MWBase::Environment::get().getInputManager()->update(evt.timeSinceLastFrame, false); + MWBase::Environment::get().getInputManager()->update(frametime, false); // sound if (mUseSound) - MWBase::Environment::get().getSoundManager()->update (evt.timeSinceLastFrame); + MWBase::Environment::get().getSoundManager()->update(frametime); // global scripts MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); @@ -86,23 +95,19 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // passing of time if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->advanceTime ( - mEnvironment.getFrameDuration()*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + MWBase::Environment::get().getWorld()->advanceTime( + frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); if (changed) // keep change flag for another frame, if cell changed happend in local script MWBase::Environment::get().getWorld()->markCellAsUnchanged(); // update actors - std::vector > movement; - MWBase::Environment::get().getMechanicsManager()->update (movement, mEnvironment.getFrameDuration(), + MWBase::Environment::get().getMechanicsManager()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode()); - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->doPhysics (movement, mEnvironment.getFrameDuration()); - // update world - MWBase::Environment::get().getWorld()->update (evt.timeSinceLastFrame, MWBase::Environment::get().getWindowManager()->isGuiMode()); + MWBase::Environment::get().getWorld()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode()); // update GUI Ogre::RenderWindow* window = mOgre->getWindow(); @@ -110,7 +115,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::Environment::get().getWorld()->getTriangleBatchCount(tri, batch); MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch); - MWBase::Environment::get().getWindowManager()->onFrame(evt.timeSinceLastFrame); + MWBase::Environment::get().getWindowManager()->onFrame(frametime); } catch (const std::exception& e) { @@ -163,9 +168,6 @@ void OMW::Engine::loadBSA() dataDirectory = iter->string(); std::cout << "Data dir " << dataDirectory << std::endl; Bsa::addDir(dataDirectory, mFSStrict); - - // Workaround until resource listing capabilities are added to DirArchive, we need those to list available splash screens - addResourcesDirectory (dataDirectory); } } @@ -212,18 +214,31 @@ void OMW::Engine::setCell (const std::string& cellName) // Set master file (esm) // - If the given name does not have an extension, ".esm" is added automatically -// - Currently OpenMW only supports one master at the same time. void OMW::Engine::addMaster (const std::string& master) { - assert (mMaster.empty()); - mMaster = master; + mMaster.push_back(master); + std::string &str = mMaster.back(); // Append .esm if not already there - std::string::size_type sep = mMaster.find_last_of ("."); + std::string::size_type sep = str.find_last_of ("."); if (sep == std::string::npos) { - mMaster += ".esm"; + str += ".esm"; + } +} + +// Add plugin file (esp) +void OMW::Engine::addPlugin (const std::string& plugin) +{ + mPlugins.push_back(plugin); + std::string &str = mPlugins.back(); + + // Append .esp if not already there + std::string::size_type sep = str.find_last_of ("."); + if (sep == std::string::npos) + { + str += ".esp"; } } @@ -242,18 +257,9 @@ void OMW::Engine::setNewGame(bool newGame) mNewGame = newGame; } -// Initialise and enter main loop. - -void OMW::Engine::go() +std::string OMW::Engine::loadSettings (Settings::Manager & settings) { - assert (!mCellName.empty()); - assert (!mMaster.empty()); - assert (!mOgre); - - mOgre = new OEngine::Render::OgreRenderer; - // Create the settings manager and load default settings file - Settings::Manager settings; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; @@ -274,10 +280,6 @@ void OMW::Engine::go() else if (boost::filesystem::exists(globaldefault)) settings.loadUser(globaldefault); - // Get the path for the keybinder xml file - std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); - bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - mFpsLevel = settings.getInt("fps", "HUD"); // load nif overrides @@ -287,6 +289,13 @@ void OMW::Engine::go() else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg")) nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"); + return settingspath; +} + +void OMW::Engine::prepareEngine (Settings::Manager & settings) +{ + Nif::NIFFile::CacheLock cachelock; + std::string renderSystem = settings.getString("render system", "Video"); if (renderSystem == "") { @@ -296,6 +305,9 @@ void OMW::Engine::go() renderSystem = "OpenGL Rendering Subsystem"; #endif } + + mOgre = new OEngine::Render::OgreRenderer; + mOgre->configure( mCfgMgr.getLogPath().string(), renderSystem, @@ -311,7 +323,6 @@ void OMW::Engine::go() addResourcesDirectory(mResDir / "mygui"); addResourcesDirectory(mResDir / "water"); - addResourcesDirectory(mResDir / "gbuffer"); addResourcesDirectory(mResDir / "shadows"); addZipResource(mResDir / "mygui" / "Obliviontt.zip"); @@ -327,19 +338,22 @@ void OMW::Engine::go() loadBSA(); - // cursor replacer (converts the cursor from the bsa so they can be used by mygui) - MWGui::CursorReplace replacer; - // Create the world - mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, - mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoding, mFallbackMap)); + mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, + mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoder, mFallbackMap, + mActivationDistanceOverride)); + + //Load translation data + mTranslationDataStorage.setEncoder(mEncoder); + for (size_t i = 0; i < mMaster.size(); i++) + mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[i]); // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); mEnvironment.setWindowManager (new MWGui::WindowManager( mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode)); + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage)); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -356,9 +370,14 @@ void OMW::Engine::go() // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); - mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts)); + mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); // Sets up the input system + + // Get the path for the keybinder xml file + std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); + bool keybinderUserExists = boost::filesystem::exists(keybinderUser); + mEnvironment.setInputManager (new MWInput::InputManager (*mOgre, MWBase::Environment::get().getWorld()->getPlayer(), *MWBase::Environment::get().getWindowManager(), mDebug, *this, keybinderUser, keybinderUserExists)); @@ -382,13 +401,8 @@ void OMW::Engine::go() MWBase::Environment::get().getWorld()->changeToInteriorCell (mCellName, pos); } - std::cout << "\nPress Q/ESC or close window to exit.\n"; - mOgre->getRoot()->addFrameListener (this); - // Play some good 'ol tunes - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - // scripts if (mCompileAll) { @@ -401,17 +415,42 @@ void OMW::Engine::go() << "%)" << std::endl; } +} + +// Initialise and enter main loop. + +void OMW::Engine::go() +{ + assert (!mCellName.empty()); + assert (!mMaster.empty()); + assert (!mOgre); + + Settings::Manager settings; + std::string settingspath; + + settingspath = loadSettings (settings); + + // Create encoder + ToUTF8::Utf8Encoder encoder (mEncoding); + mEncoder = &encoder; + + prepareEngine (settings); + + // Play some good 'ol tunes + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); if (!mStartupScript.empty()) MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + std::cout << "\nPress Q/ESC or close window to exit.\n"; + // Start the main rendering loop mOgre->start(); // Save user settings settings.saveUser(settingspath); - std::cout << "Quitting peacefully.\n"; + std::cout << "Quitting peacefully." << std::endl; } void OMW::Engine::activate() @@ -419,12 +458,7 @@ void OMW::Engine::activate() if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); - - if (handle.empty()) - return; - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle (handle); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); if (ptr.isEmpty()) return; @@ -487,7 +521,7 @@ void OMW::Engine::showFPS(int level) mFpsLevel = level; } -void OMW::Engine::setEncoding(const std::string& encoding) +void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; } @@ -506,3 +540,9 @@ void OMW::Engine::setStartupScript (const std::string& path) { mStartupScript = path; } + + +void OMW::Engine::setActivationDistanceOverride (int distance) +{ + mActivationDistanceOverride = distance; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 57402c91e..a4acee523 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include "mwbase/environment.hpp" @@ -59,12 +61,14 @@ namespace OMW class Engine : private Ogre::FrameListener { MWBase::Environment mEnvironment; - std::string mEncoding; + ToUTF8::FromType mEncoding; + ToUTF8::Utf8Encoder* mEncoder; Files::PathContainer mDataDirs; boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::string mMaster; + std::vector mMaster; + std::vector mPlugins; int mFpsLevel; bool mDebug; bool mVerboseScripts; @@ -75,13 +79,14 @@ namespace OMW std::map mFallbackMap; bool mScriptConsoleMode; std::string mStartupScript; + int mActivationDistanceOverride; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; - Files::Collections mFileCollections; bool mFSStrict; + Translation::Storage mTranslationDataStorage; // not implemented Engine (const Engine&); @@ -100,6 +105,13 @@ namespace OMW void executeLocalScripts(); virtual bool frameRenderingQueued (const Ogre::FrameEvent& evt); + virtual bool frameStarted (const Ogre::FrameEvent& evt); + + /// Load settings from various files, returns the path to the user settings file + std::string loadSettings (Settings::Manager & settings); + + /// Prepare engine for game play + void prepareEngine (Settings::Manager & settings); public: Engine(Files::ConfigurationManager& configurationManager); @@ -122,9 +134,12 @@ namespace OMW /// Set master file (esm) /// - If the given name does not have an extension, ".esm" is added automatically - /// - Currently OpenMW only supports one master at the same time. void addMaster(const std::string& master); + /// Same as "addMaster", but for plugin files (esp) + /// - If the given name does not have an extension, ".esp" is added automatically + void addPlugin(const std::string& plugin); + /// Enable fps counter void showFPS(int level); @@ -154,7 +169,7 @@ namespace OMW void setCompileAll (bool all); /// Font encoding - void setEncoding(const std::string& encoding); + void setEncoding(const ToUTF8::FromType& encoding); void setAnimationVerbose(bool animverbose); @@ -166,6 +181,9 @@ namespace OMW /// Set path for a script that is run on startup in the console. void setStartupScript (const std::string& path); + /// Override the game setting specified activation distance. + void setActivationDistanceOverride (int distance); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 0563fdbbb..86978c9b1 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -149,6 +149,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); + ; bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -181,21 +183,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // Font encoding settings std::string encoding(variables["encoding"].as()); - if (encoding == "win1250") - { - std::cout << "Using Central and Eastern European font encoding." << std::endl; - engine.setEncoding(encoding); - } - else if (encoding == "win1251") - { - std::cout << "Using Cyrillic font encoding." << std::endl; - engine.setEncoding(encoding); - } - else - { - std::cout << "Using default (English) font encoding." << std::endl; - engine.setEncoding("win1252"); - } + std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; + engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings engine.enableFSStrict(variables["fs-strict"].as()); @@ -222,18 +211,21 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat master.push_back("Morrowind"); } - if (master.size() > 1) - { - std::cout - << "Ignoring all but the first master file (multiple master files not yet supported)." - << std::endl; - } - engine.addMaster(master[0]); - StringsVector plugin = variables["plugin"].as(); - if (!plugin.empty()) + // Removed check for 255 files, which would be the hard-coded limit in Morrowind. + // I'll keep the following variable in, maybe we can use it for something different. + // Say, a feedback like "loading file x/cnt". + // Commenting this out for now to silence compiler warning. + //int cnt = master.size() + plugin.size(); + + // Prepare loading master/plugin files (i.e. send filenames to engine) + for (std::vector::size_type i = 0; i < master.size(); i++) { - std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl; + engine.addMaster(master[i]); + } + for (std::vector::size_type i = 0; i < plugin.size(); i++) + { + engine.addPlugin(plugin[i]); } // startup-settings @@ -249,6 +241,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setFallbackValues(variables["fallback"].as().mMap); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); + engine.setActivationDistanceOverride (variables["activate-dist"].as()); return true; } diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 9aaa5af85..5a13a50ec 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -128,12 +128,6 @@ float MWBase::Environment::getFrameDuration() const void MWBase::Environment::cleanup() { - delete mInputManager; - mInputManager = 0; - - delete mSoundManager; - mSoundManager = 0; - delete mMechanicsManager; mMechanicsManager = 0; @@ -146,11 +140,17 @@ void MWBase::Environment::cleanup() delete mScriptManager; mScriptManager = 0; + delete mWorld; + mWorld = 0; + + delete mSoundManager; + mSoundManager = 0; + delete mWindowManager; mWindowManager = 0; - delete mWorld; - mWorld = 0; + delete mInputManager; + mInputManager = 0; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index a1023c986..b8733259f 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -37,24 +37,24 @@ namespace MWBase virtual ~MechanicsManager() {} - virtual void addActor (const MWWorld::Ptr& ptr) = 0; - ///< Register an actor for stats management - /// - /// \note Dead actors are ignored. + virtual void add (const MWWorld::Ptr& ptr) = 0; + ///< Register an object for management - virtual void removeActor (const MWWorld::Ptr& ptr) = 0; - ///< Deregister an actor for stats management + virtual void remove (const MWWorld::Ptr& ptr) = 0; + ///< Deregister an object for management - virtual void dropActors (const MWWorld::CellStore *cellStore) = 0; - ///< Deregister all actors in the given cell. + virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0; + ///< Moves an object to a new cell + + virtual void drop (const MWWorld::CellStore *cellStore) = 0; + ///< Deregister all objects in the given cell. virtual void watchActor (const MWWorld::Ptr& ptr) = 0; ///< On each update look for changes in a previously registered actor and update the /// GUI accordingly. - virtual void update (std::vector >& movement, - float duration, bool paused) = 0; - ///< Update actor stats and store desired velocity vectors in \a movement + virtual void update (float duration, bool paused) = 0; + ///< Update objects /// /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). @@ -98,6 +98,17 @@ namespace MWBase virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) = 0; ///< Perform a persuasion action on NPC + + virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; + ///< Run animation for a MW-reference. Calls to this function for references that are currently not + /// in the scene should be ignored. + /// + /// \param mode 0 normal, 1 immediate start, 2 immediate loop + /// \param count How many times the animation should be run + + virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; + ///< Skip the animation for the given MW-reference for one frame. Calls to this function for + /// references that are currently not in the scene should be ignored. }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 92c177ff3..5d396fac0 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -22,6 +22,8 @@ namespace MWWorld namespace MWSound { class Sound; + class Sound_Decoder; + typedef boost::shared_ptr DecoderPtr; } namespace MWBase @@ -32,7 +34,7 @@ namespace MWBase class SoundManager { public: - + /* These must all fit together */ enum PlayMode { Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ @@ -41,6 +43,13 @@ namespace MWBase * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ }; + enum PlayType { + Play_TypeSfx = 1<<3, /* Normal SFX sound */ + Play_TypeVoice = 1<<4, /* Voice sound */ + Play_TypeMusic = 1<<5, /* Music track */ + Play_TypeMovie = 1<<6, /* Movie audio track */ + Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeMusic|Play_TypeMovie + }; private: @@ -75,7 +84,7 @@ namespace MWBase ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(MWWorld::Ptr reference, const std::string& filename) = 0; + virtual void say(const MWWorld::Ptr &reference, const std::string& filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -83,24 +92,27 @@ namespace MWBase ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(MWWorld::Ptr reference=MWWorld::Ptr()) const = 0; + virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const = 0; ///< Is actor not speaking? - virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()) = 0; + virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0; ///< Stop an actor speaking + virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; + ///< Play a 2D audio track, using a custom decoder + virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch, - int mode=Play_Normal) = 0; + PlayMode mode=Play_Normal) = 0; ///< Play a sound, independently of 3D-position - virtual SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal) = 0; + virtual SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + float volume, float pitch, PlayMode mode=Play_Normal) = 0; ///< Play a sound from an object - virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId) = 0; + virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0; ///< Stop the given object from playing the given sound, - virtual void stopSound3D(MWWorld::Ptr reference) = 0; + virtual void stopSound3D(const MWWorld::Ptr &reference) = 0; ///< Stop the given object from playing all sounds. virtual void stopSound(const MWWorld::CellStore *cell) = 0; @@ -109,18 +121,19 @@ namespace MWBase virtual void stopSound(const std::string& soundId) = 0; ///< Stop a non-3d looping sound - virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const = 0; + virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const = 0; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask) = 0; + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask) = 0; + ///< Resumes all previously paused sounds. + virtual void update(float duration) = 0; virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; }; - - inline int operator|(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) | static_cast (b); } - inline int operator&(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) & static_cast (b); } } #endif diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c177912d4..93cc8e44a 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwmechanics/stat.hpp" #include "../mwgui/mode.hpp" @@ -196,7 +198,8 @@ namespace MWBase virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0; ///< Hides dialog and schedules dialog to be deleted. - virtual void messageBox (const std::string& message, const std::vector& buttons) = 0; + virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector()) = 0; + virtual void enterPressed () = 0; virtual int readPressedButton() = 0; ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) @@ -233,6 +236,10 @@ namespace MWBase virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; + + virtual void changePointer (const std::string& name) = 0; + + virtual const Translation::Storage& getTranslationDataStorage() const = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 40ebde246..6cd5b90b4 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -6,6 +6,7 @@ #include #include "../mwworld/globals.hpp" +#include "../mwworld/ptr.hpp" namespace Ogre { @@ -19,6 +20,11 @@ namespace OEngine { class Fader; } + + namespace Physic + { + class PhysicEngine; + } } namespace ESM @@ -35,6 +41,7 @@ namespace ESM namespace MWRender { class ExternalRendering; + class Animation; } namespace MWWorld @@ -42,9 +49,11 @@ namespace MWWorld class CellStore; class Player; class LocalScripts; - class Ptr; class TimeStamp; class ESMStore; + class RefData; + + typedef std::vector > PtrMovementList; } namespace MWBase @@ -104,7 +113,7 @@ namespace MWBase virtual const MWWorld::ESMStore& getStore() const = 0; - virtual ESM::ESMReader& getEsmReader() = 0; + virtual std::vector& getEsmReader() = 0; virtual MWWorld::LocalScripts& getLocalScripts() = 0; @@ -133,6 +142,13 @@ namespace MWBase virtual char getGlobalVariableType (const std::string& name) const = 0; ///< Return ' ', if there is no global variable with this name. + + virtual std::vector getGlobals () const = 0; + + virtual std::string getCurrentCellName() const = 0; + + virtual void removeRefScript (MWWorld::RefData *ref) = 0; + //< Remove the script attached to ref from mLocalScripts virtual MWWorld::Ptr getPtr (const std::string& name, bool activeOnly) = 0; ///< Return a pointer to a liveCellRef with the given name. @@ -195,8 +211,8 @@ namespace MWBase virtual void markCellAsUnchanged() = 0; - virtual std::string getFacedHandle() = 0; - ///< Return handle of the object the player is looking at + virtual MWWorld::Ptr getFacedObject() = 0; + ///< Return pointer to the object the player is looking at, if it is within activation range virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; @@ -219,8 +235,7 @@ namespace MWBase virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; ///< Convert position to cell numbers - virtual void doPhysics (const std::vector >& actors, - float duration) = 0; + virtual void doPhysics (const MWWorld::PtrMovementList &actors, float duration) = 0; ///< Run physics simulation and modify \a world accordingly. virtual bool toggleCollisionMode() = 0; @@ -255,18 +270,6 @@ namespace MWBase ///< Create a new recrod (of type npc) in the ESM store. /// \return pointer to created record - virtual void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, - int mode, int number = 1) = 0; - ///< Run animation for a MW-reference. Calls to this function for references that are - /// currently not in the rendered scene should be ignored. - /// - /// \param mode: 0 normal, 1 immediate start, 2 immediate loop - /// \param number How offen the animation should be run - - virtual void skipAnimation (const MWWorld::Ptr& ptr) = 0; - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the rendered scene should be ignored. - virtual void update (float duration, bool paused) = 0; virtual bool placeObject(const MWWorld::Ptr& object, float cursorX, float cursorY) = 0; @@ -276,21 +279,24 @@ namespace MWBase /// @param cursor Y (relative 0-1) /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const MWWorld::Ptr& object) = 0; + virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object) = 0; virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location virtual void processChangedSettings (const Settings::CategorySettingVector& settings) = 0; - virtual bool isSwimming(const MWWorld::Ptr &object) = 0; - virtual bool isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos) = 0; + virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; + virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; + virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const = 0; + virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual void togglePOV() = 0; virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable, bool force) = 0; virtual void allowVanityMode(bool allow) = 0; virtual void togglePlayerLooking(bool enable) = 0; + virtual void changeVanityModeScale(float factor) = 0; virtual void renderPlayer() = 0; @@ -302,6 +308,14 @@ namespace MWBase /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + /// \todo Probably shouldn't be here + virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual void stopVideo() = 0; + virtual void frameStarted (float dt) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 26d286aa1..3a60d9c39 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -5,12 +5,13 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld//cellstore.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/physicssystem.hpp" -#include "../mwrender/objects.hpp" +#include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" @@ -21,9 +22,8 @@ namespace MWClass { const std::string model = getModel(ptr); if (!model.empty()) { - MWRender::Objects& objects = renderingInterface.getObjects(); - objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, model); + MWRender::Actors& actors = renderingInterface.getActors(); + actors.insertActivator(ptr); } } @@ -32,6 +32,7 @@ namespace MWClass const std::string model = getModel(ptr); if(!model.empty()) physics.addObject(ptr); + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Activator::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 06467bb21..2c561eb85 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -12,6 +12,7 @@ #include "../mwworld/actionalchemy.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -61,6 +62,9 @@ namespace MWClass boost::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 704173b1c..654cb87fd 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -15,6 +15,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -64,6 +65,9 @@ namespace MWClass boost::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index c411bb193..892ac091c 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -13,6 +13,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -62,6 +63,9 @@ namespace MWClass boost::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 76c1c40f1..a2d75131e 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -9,6 +9,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/failedaction.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" @@ -85,6 +86,9 @@ namespace MWClass boost::shared_ptr Container::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + const std::string lockedSound = "LockedChest"; const std::string trapActivationSound = "Disarm Trap Fail"; @@ -97,11 +101,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().mKey; - std::transform(keyId.begin(), keyId.end(), keyId.begin(), ::tolower); + Misc::StringUtils::toLower(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().mRefID; - std::transform(refId.begin(), refId.end(), refId.begin(), ::tolower); + Misc::StringUtils::toLower(refId); if (refId == keyId) { hasKey = true; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index edf5dd25d..19f327dcb 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -18,6 +18,7 @@ #include "../mwworld/physicssystem.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/actors.hpp" #include "../mwgui/tooltips.hpp" @@ -96,7 +97,7 @@ namespace MWClass const std::string model = getModel(ptr); if(!model.empty()) physics.addActor(ptr); - MWBase::Environment::get().getMechanicsManager()->addActor (ptr); + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Creature::getModel(const MWWorld::Ptr &ptr) const @@ -131,7 +132,7 @@ namespace MWClass const MWWorld::Ptr& actor) const { if (MWWorld::Class::get (ptr).getCreatureStats (ptr).isDead()) - return boost::shared_ptr (new MWWorld::ActionOpen(ptr)); + return boost::shared_ptr (new MWWorld::ActionOpen(ptr, true)); else return boost::shared_ptr (new MWWorld::ActionTalk (ptr)); } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index a158fa743..a96c18a8c 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,9 +1,7 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H -#include "../mwrender/renderinginterface.hpp" -#include "../mwrender/actors.hpp" - +#include "../mwworld/class.hpp" namespace MWClass { diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 843d1af4c..fb6329939 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -84,11 +84,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().mKey; - std::transform(keyId.begin(), keyId.end(), keyId.begin(), ::tolower); + Misc::StringUtils::toLower(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().mRefID; - std::transform(refId.begin(), refId.end(), refId.begin(), ::tolower); + Misc::StringUtils::toLower(refId); if (refId == keyId) { hasKey = true; @@ -204,33 +204,10 @@ namespace MWClass std::string text; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - if (ref->mRef.mTeleport) { - std::string dest; - if (ref->mRef.mDestCell != "") - { - // door leads to an interior, use interior name as tooltip - dest = ref->mRef.mDestCell; - } - else - { - // door leads to exterior, use cell name (if any), otherwise translated region name - int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (ref->mRef.mDoorDest.pos[0], ref->mRef.mDoorDest.pos[1], x, y); - const ESM::Cell* cell = store.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - const ESM::Region* region = - store.get().find(cell->mRegion); - dest = region->mName; - } - } text += "\n#{sTo}"; - text += "\n"+dest; + text += "\n" + getDestination(*ref); } if (ref->mRef.mLockLevel > 0) @@ -246,6 +223,37 @@ namespace MWClass return info; } + std::string Door::getDestination (const MWWorld::LiveCellRef& door) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string dest; + if (door.mRef.mDestCell != "") + { + // door leads to an interior, use interior name as tooltip + dest = door.mRef.mDestCell; + } + else + { + // door leads to exterior, use cell name (if any), otherwise translated region name + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.mDoorDest.pos[0], door.mRef.mDoorDest.pos[1], x, y); + const ESM::Cell* cell = store.get().find(x,y); + if (cell->mName != "") + dest = cell->mName; + else + { + const ESM::Region* region = + store.get().find(cell->mRegion); + + //name as is, not a token + return region->mName; + } + } + + return "#{sCell=" + dest + "}"; + } + MWWorld::Ptr Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index b0f86f12d..05ba0248b 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -31,6 +31,9 @@ namespace MWClass virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + static std::string getDestination (const MWWorld::LiveCellRef& door); + ///< @return destination cell name or token + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; ///< Lock object diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 1be8d66b3..bbba45df5 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -12,6 +12,10 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/actioneat.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/nullaction.hpp" + +#include "../mwmechanics/npcstats.hpp" #include "../mwgui/tooltips.hpp" @@ -69,6 +73,9 @@ namespace MWClass boost::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); @@ -154,6 +161,10 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); + int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) { @@ -163,6 +174,12 @@ namespace MWClass params.mEffectID = ref->mBase->mData.mEffectID[i]; params.mAttribute = ref->mBase->mData.mAttributes[i]; params.mSkill = ref->mBase->mData.mSkills[i]; + + params.mKnown = ( (i == 0 && alchemySkill >= 15) + || (i == 1 && alchemySkill >= 30) + || (i == 2 && alchemySkill >= 45) + || (i == 3 && alchemySkill >= 60)); + list.push_back(params); } info.effects = list; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index b94b0d395..200f6e2d4 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -36,14 +36,9 @@ namespace MWClass objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); if (!model.empty()) - objects.insertMesh(ptr, "meshes\\" + model); - - const int color = ref->mBase->mData.mColor; - const float r = ((color >> 0) & 0xFF) / 255.0f; - const float g = ((color >> 8) & 0xFF) / 255.0f; - const float b = ((color >> 16) & 0xFF) / 255.0f; - const float radius = float (ref->mBase->mData.mRadius); - objects.insertLight (ptr, r, g, b, radius); + objects.insertMesh(ptr, "meshes\\" + model, true); + else + objects.insertLight(ptr); } void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const @@ -88,6 +83,9 @@ namespace MWClass boost::shared_ptr Light::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index a667fefb2..7e909437c 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -13,6 +13,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -62,6 +63,9 @@ namespace MWClass boost::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 94d40bcb7..d43a44359 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -14,6 +14,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -65,6 +66,9 @@ namespace MWClass boost::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); @@ -210,6 +214,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = newRef.getPtr().get(); newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell); + newPtr.getRefData ().setCount(goldAmount); } else { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 83a002447..f074d0368 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -55,9 +55,35 @@ namespace MWClass { void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const { + static bool inited = false; + if(!inited) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); + + fMinWalkSpeed = gmst.find("fMinWalkSpeed"); + fMaxWalkSpeed = gmst.find("fMaxWalkSpeed"); + fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect"); + fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier"); + fAthleticsRunBonus = gmst.find("fAthleticsRunBonus"); + fBaseRunMultiplier = gmst.find("fBaseRunMultiplier"); + fMinFlySpeed = gmst.find("fMinFlySpeed"); + fMaxFlySpeed = gmst.find("fMaxFlySpeed"); + fSwimRunBase = gmst.find("fSwimRunBase"); + fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult"); + fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase"); + fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier"); + fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase"); + fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier"); + fJumpRunMultiplier = gmst.find("fJumpRunMultiplier"); + // Added in Tribunal/Bloodmoon, may not exist + fWereWolfRunMult = gmst.search("fWereWolfRunMult"); + + inited = true; + } if (!ptr.getRefData().getCustomData()) { - std::auto_ptr data (new CustomData); + std::auto_ptr data(new CustomData); MWWorld::LiveCellRef *ref = ptr.get(); @@ -65,7 +91,7 @@ namespace MWClass if (!ref->mBase->mFaction.empty()) { std::string faction = ref->mBase->mFaction; - boost::algorithm::to_lower(faction); + Misc::StringUtils::toLower(faction); if(ref->mBase->mNpdt52.mGold != -10) { data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank; @@ -100,14 +126,15 @@ namespace MWClass } else { - /// \todo do something with mNpdt12 maybe:p for (int i=0; i<8; ++i) data->mCreatureStats.getAttribute (i).set (10); for (int i=0; i<3; ++i) data->mCreatureStats.setDynamic (i, 10); - data->mCreatureStats.setLevel (1); + data->mCreatureStats.setLevel(ref->mBase->mNpdt12.mLevel); + data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition); + data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); } data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello); @@ -141,7 +168,7 @@ namespace MWClass void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const { physics.addActor(ptr); - MWBase::Environment::get().getMechanicsManager()->addActor(ptr); + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Npc::getModel(const MWWorld::Ptr &ptr) const @@ -193,7 +220,9 @@ namespace MWClass const MWWorld::Ptr& actor) const { if (MWWorld::Class::get (ptr).getCreatureStats (ptr).isDead()) - return boost::shared_ptr (new MWWorld::ActionOpen(ptr)); + return boost::shared_ptr (new MWWorld::ActionOpen(ptr, true)); + else if (MWWorld::Class::get(actor).getStance(actor, MWWorld::Class::Sneak)) + return boost::shared_ptr (new MWWorld::ActionOpen(ptr)); // stealing else return boost::shared_ptr (new MWWorld::ActionTalk (ptr)); } @@ -296,9 +325,87 @@ namespace MWClass return false; } - float Npc::getSpeed (const MWWorld::Ptr& ptr) const + float Npc::getSpeed(const MWWorld::Ptr& ptr) const { - return getStance (ptr, Run) ? 600 : 300; // TODO calculate these values from stats + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const MWMechanics::MagicEffects &mageffects = npcdata->mCreatureStats.getMagicEffects(); + + const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr); + + float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mCreatureStats.getAttribute(ESM::Attribute::Speed).getModified()* + (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat()); + walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; + walkSpeed = std::max(0.0f, walkSpeed); + if(Npc::getStance(ptr, Sneak, false)) + walkSpeed *= fSneakSpeedMultiplier->getFloat(); + + float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * + fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + if(npcdata->mNpcStats.isWerewolf()) + runSpeed *= fWereWolfRunMult->getFloat(); + + float moveSpeed; + if(normalizedEncumbrance >= 1.0f) + moveSpeed = 0.0f; + else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0) + { + float flySpeed = 0.01f*(npcdata->mCreatureStats.getAttribute(ESM::Attribute::Speed).getModified() + + mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude); + flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); + flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = std::max(0.0f, flySpeed); + moveSpeed = flySpeed; + } + else if(world->isSwimming(ptr)) + { + float swimSpeed = walkSpeed; + if(Npc::getStance(ptr, Run, false)) + swimSpeed = runSpeed; + swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude; + swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* + fSwimRunAthleticsMult->getFloat(); + moveSpeed = swimSpeed; + } + else if(Npc::getStance(ptr, Run, false) && !Npc::getStance(ptr, Sneak, false)) + moveSpeed = runSpeed; + else + moveSpeed = walkSpeed; + if(getMovementSettings(ptr).mLeftRight != 0 && getMovementSettings(ptr).mForwardBackward == 0) + moveSpeed *= 0.75f; + + return moveSpeed; + } + + float Npc::getJump(const MWWorld::Ptr &ptr) const + { + const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const MWMechanics::MagicEffects &mageffects = npcdata->mCreatureStats.getMagicEffects(); + const float encumbranceTerm = fJumpEncumbranceBase->getFloat() + + fJumpEncumbranceMultiplier->getFloat() * + (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); + + float a = npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified(); + float b = 0.0f; + if(a > 50.0f) + { + b = a - 50.0f; + a = 50.0f; + } + + float x = fJumpAcrobaticsBase->getFloat() + + std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat()); + x += 3 * b * fJumpAcroMultiplier->getFloat(); + x += mageffects.get(MWMechanics::EffectKey(9/*jump*/)).mMagnitude * 64; + x *= encumbranceTerm; + + if(Npc::getStance(ptr, Run, false)) + x *= fJumpRunMultiplier->getFloat(); + x *= 1.25f;//fatigueTerm; + x -= -627.2/*gravity constant*/; + x /= 3; + + return x; } MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const @@ -310,14 +417,10 @@ namespace MWClass Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const { - Ogre::Vector3 vector (0, 0, 0); - - vector.x = getMovementSettings (ptr).mLeftRight * 127; - vector.y = getMovementSettings (ptr).mForwardBackward * 127; - vector.z = getMovementSettings(ptr).mUpDown * 127; - - //if (getStance (ptr, Run, false)) - // vector *= 2; + Ogre::Vector3 vector; + vector.x = getMovementSettings(ptr).mLeftRight; + vector.y = getMovementSettings(ptr).mForwardBackward; + vector.z = getMovementSettings(ptr).mUpDown; return vector; } @@ -419,4 +522,21 @@ namespace MWClass return MWWorld::Ptr(&cell.mNpcs.insert(*ref), &cell); } + + const ESM::GameSetting *Npc::fMinWalkSpeed; + const ESM::GameSetting *Npc::fMaxWalkSpeed; + const ESM::GameSetting *Npc::fEncumberedMoveEffect; + const ESM::GameSetting *Npc::fSneakSpeedMultiplier; + const ESM::GameSetting *Npc::fAthleticsRunBonus; + const ESM::GameSetting *Npc::fBaseRunMultiplier; + const ESM::GameSetting *Npc::fMinFlySpeed; + const ESM::GameSetting *Npc::fMaxFlySpeed; + const ESM::GameSetting *Npc::fSwimRunBase; + const ESM::GameSetting *Npc::fSwimRunAthleticsMult; + const ESM::GameSetting *Npc::fJumpEncumbranceBase; + const ESM::GameSetting *Npc::fJumpEncumbranceMultiplier; + const ESM::GameSetting *Npc::fJumpAcrobaticsBase; + const ESM::GameSetting *Npc::fJumpAcroMultiplier; + const ESM::GameSetting *Npc::fJumpRunMultiplier; + const ESM::GameSetting *Npc::fWereWolfRunMult; } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 20c2da4b1..f41edb0df 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -3,6 +3,11 @@ #include "../mwworld/class.hpp" +namespace ESM +{ + class GameSetting; +} + namespace MWClass { class Npc : public MWWorld::Class @@ -12,6 +17,23 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + static const ESM::GameSetting *fMinWalkSpeed; + static const ESM::GameSetting *fMaxWalkSpeed; + static const ESM::GameSetting *fEncumberedMoveEffect; + static const ESM::GameSetting *fSneakSpeedMultiplier; + static const ESM::GameSetting *fAthleticsRunBonus; + static const ESM::GameSetting *fBaseRunMultiplier; + static const ESM::GameSetting *fMinFlySpeed; + static const ESM::GameSetting *fMaxFlySpeed; + static const ESM::GameSetting *fSwimRunBase; + static const ESM::GameSetting *fSwimRunAthleticsMult; + static const ESM::GameSetting *fJumpEncumbranceBase; + static const ESM::GameSetting *fJumpEncumbranceMultiplier; + static const ESM::GameSetting *fJumpAcrobaticsBase; + static const ESM::GameSetting *fJumpAcroMultiplier; + static const ESM::GameSetting *fJumpRunMultiplier; + static const ESM::GameSetting *fWereWolfRunMult; + public: virtual std::string getId (const MWWorld::Ptr& ptr) const; @@ -64,6 +86,9 @@ namespace MWClass virtual float getSpeed (const MWWorld::Ptr& ptr) const; ///< Return movement speed. + virtual float getJump(const MWWorld::Ptr &ptr) const; + ///< Return jump velocity (not accounting for movement) + virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 09d152de7..0ac78a2e4 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -13,12 +13,15 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwmechanics/npcstats.hpp" + namespace MWClass { void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -62,6 +65,9 @@ namespace MWClass boost::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action( new MWWorld::ActionTake (ptr)); @@ -134,6 +140,23 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); + + // hide effects the player doesnt know about + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); + int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + int i=0; + for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) + { + /// \todo this code is duplicated from mwclass/ingredient, put it in a helper function + it->mKnown = ( (i == 0 && alchemySkill >= 15) + || (i == 1 && alchemySkill >= 30) + || (i == 2 && alchemySkill >= 45) + || (i == 3 && alchemySkill >= 60)); + + ++i; + } + info.isPotion = true; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 0d8653aa8..a28be17e7 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -13,6 +13,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -61,6 +62,9 @@ namespace MWClass boost::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index edb28d16c..39a7f65e0 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -11,6 +11,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -60,6 +61,9 @@ namespace MWClass boost::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index c8fe0d276..d8c11558c 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -13,6 +13,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -62,6 +63,9 @@ namespace MWClass boost::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return boost::shared_ptr (new MWWorld::NullAction ()); + boost::shared_ptr action(new MWWorld::ActionTake (ptr)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 80316c0f5..35f0c9493 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -2,6 +2,7 @@ #include "dialoguemanagerimp.hpp" #include +#include #include #include @@ -16,9 +17,11 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -39,47 +42,14 @@ #include "filter.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - - bool stringCompareNoCase (std::string first, std::string second) - { - unsigned int i=0; - while ( (itolower(second[i])) return false; - ++i; - } - if (first.length()::iterator it = dialogs.begin(); for (; it != dialogs.end(); ++it) { - mDialogueMap[toLower(it->mId)] = *it; + mDialogueMap[Misc::StringUtils::lowerCase(it->mId)] = *it; } } void DialogueManager::addTopic (const std::string& topic) { - mKnownTopics[toLower(topic)] = true; + mKnownTopics[Misc::StringUtils::lowerCase(topic)] = true; } void DialogueManager::parseText (const std::string& text) { - std::list::iterator it; - for(it = mActorKnownTopics.begin();it != mActorKnownTopics.end();++it) + std::vector hypertext = ParseHyperText(text); + + //calculation of standard form fir all hyperlinks + for (size_t i = 0; i < hypertext.size(); ++i) { - size_t pos = find_str_ci(text,*it,0); - if(pos !=std::string::npos) + if (hypertext[i].mLink) { - mKnownTopics[*it] = true; + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + for(; asterisk_count > 0; --asterisk_count) + hypertext[i].mText.append("*"); + + hypertext[i].mText = mTranslationDataStorage.topicStandardForm(hypertext[i].mText); } } + + for (size_t i = 0; i < hypertext.size(); ++i) + { + std::list::iterator it; + for(it = mActorKnownTopics.begin(); it != mActorKnownTopics.end(); ++it) + { + if (hypertext[i].mLink) + { + if( hypertext[i].mText == *it ) + { + mKnownTopics[hypertext[i].mText] = true; + } + } + else if( !mTranslationDataStorage.hasTranslation() ) + { + size_t pos = Misc::StringUtils::lowerCase(hypertext[i].mText).find(*it, 0); + if(pos !=std::string::npos) + { + mKnownTopics[*it] = true; + } + } + } + } + updateTopics(); } @@ -125,15 +124,9 @@ namespace MWDialogue MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); mTalkedTo = creatureStats.hasTalkedToPlayer(); - creatureStats.talkedToPlayer(); mActorKnownTopics.clear(); - //initialise the GUI - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor)); - //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); @@ -147,15 +140,25 @@ namespace MWDialogue { if(it->mType == ESM::Dialogue::Greeting) { - if (const ESM::DialInfo *info = filter.search (*it)) + // Search a response (we do not accept a fallback to "Info refusal" here) + if (const ESM::DialInfo *info = filter.search (*it, false)) { + //initialise the GUI + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue); + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor)); + + creatureStats.talkedToPlayer(); + if (!info->mSound.empty()) { // TODO play sound } parseText (info->mResponse); - win->addText (info->mResponse); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = it->mId; mLastDialogue = *info; @@ -247,11 +250,15 @@ namespace MWDialogue const ESM::Dialogue& dialogue = *dialogues.find (topic); - if (const ESM::DialInfo *info = filter.search (dialogue)) - { - parseText (info->mResponse); + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + std::vector infos = filter.list (dialogue, true, true); + + if (!infos.empty()) + { + const ESM::DialInfo* info = infos[std::rand() % infos.size()]; + + parseText (info->mResponse); if (dialogue.mType==ESM::Dialogue::Persuasion) { @@ -267,13 +274,22 @@ namespace MWDialogue else win->addTitle (topic); - win->addText (info->mResponse); + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId); executeScript (info->mResultScript); mLastTopic = topic; mLastDialogue = *info; } + else + { + // no response found, print a fallback text + win->addTitle (topic); + win->addText ("…"); + + } } void DialogueManager::updateTopics() @@ -292,12 +308,13 @@ namespace MWDialogue { if (iter->mType == ESM::Dialogue::Topic) { - if (filter.search (*iter)) + if (filter.responseAvailable (*iter)) { - mActorKnownTopics.push_back (toLower (iter->mId)); + std::string lower = Misc::StringUtils::lowerCase(iter->mId); + mActorKnownTopics.push_back (lower); //does the player know the topic? - if (mKnownTopics.find (toLower (iter->mId)) != mKnownTopics.end()) + if (mKnownTopics.find (lower) != mKnownTopics.end()) { keywordList.push_back (iter->mId); } @@ -355,7 +372,7 @@ namespace MWDialogue win->setServices (windowServices); // sort again, because the previous sort was case-sensitive - keywordList.sort(stringCompareNoCase); + keywordList.sort(Misc::StringUtils::ciEqual); win->setKeywords(keywordList); mChoice = choice; @@ -380,6 +397,10 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { + // Do not close the dialogue window if the player has to answer a question + if (mIsInChoice) + return; + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); // Apply disposition change to NPC's base disposition @@ -404,14 +425,17 @@ namespace MWDialogue { Filter filter (mActor, mChoice, mTalkedTo); - if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic])) + if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true)) { mChoiceMap.clear(); mChoice = -1; mIsInChoice = false; std::string text = info->mResponse; parseText (text); - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (text); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext)); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); executeScript (info->mResultScript); mLastTopic = mLastTopic; mLastDialogue = *info; @@ -433,7 +457,7 @@ namespace MWDialogue { MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->askQuestion(question); - mChoiceMap[toLower(question)] = choice; + mChoiceMap[Misc::StringUtils::lowerCase(question)] = choice; mIsInChoice = true; } @@ -493,4 +517,57 @@ namespace MWDialogue { mTemporaryDispositionChange += delta; } + + std::vector ParseHyperText(const std::string& text) + { + std::vector result; + + MyGUI::UString utext(text); + + size_t pos_begin, pos_end, iteration_pos = 0; + for(;;) + { + pos_begin = utext.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = utext.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + result.push_back( HyperTextToken(utext.substr(iteration_pos, pos_begin - iteration_pos), false) ); + + std::string link = utext.substr(pos_begin + 1, pos_end - pos_begin - 1); + result.push_back( HyperTextToken(link, true) ); + + iteration_pos = pos_end + 1; + } + else + { + result.push_back( HyperTextToken(utext.substr(iteration_pos), false) ); + break; + } + } + + return result; + } + + size_t RemovePseudoAsterisks(std::string& phrase) + { + size_t pseudoAsterisksCount = 0; + const char specialPseudoAsteriskCharacter = 127; + + if( !phrase.empty() ) + { + std::string::reverse_iterator rit = phrase.rbegin(); + + while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + { + pseudoAsterisksCount++; + ++rit; + } + } + + phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); + + return pseudoAsterisksCount; + } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 98b27f774..1ca2ae5eb 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -20,6 +21,7 @@ namespace MWDialogue std::map mKnownTopics;// Those are the topics the player knows. std::list mActorKnownTopics; + Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; std::ostream mErrorStream; Compiler::StreamErrorHandler mErrorHandler; @@ -50,7 +52,7 @@ namespace MWDialogue public: - DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose); + DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage); virtual void startDialogue (const MWWorld::Ptr& actor); @@ -72,6 +74,21 @@ namespace MWDialogue virtual int getTemporaryDispositionChange () const; virtual void applyTemporaryDispositionChange (int delta); }; + + + struct HyperTextToken + { + HyperTextToken(const std::string& text, bool link) : mText(text), mLink(link) {} + + std::string mText; + bool mLink; + }; + + // In translations (at least Russian) the links are marked with @#, so + // it should be a function to parse it + std::vector ParseHyperText(const std::string& text); + + size_t RemovePseudoAsterisks(std::string& phrase); } #endif diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 0deef7fd0..10740794e 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -17,27 +17,21 @@ #include "selectwrapper.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } -} - bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const { + bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + // actor id if (!info.mActor.empty()) - if (toLower (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor)) + { + if ( Misc::StringUtils::lowerCase (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor)) return false; - - bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + } + else if (isCreature) + { + // Creatures must not have topics aside of those specific to their id + return false; + } // NPC race if (!info.mRace.empty()) @@ -47,7 +41,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const MWWorld::LiveCellRef *cellRef = mActor.get(); - if (toLower (info.mRace)!=toLower (cellRef->mBase->mRace)) + if (Misc::StringUtils::lowerCase (info.mRace)!= Misc::StringUtils::lowerCase (cellRef->mBase->mRace)) return false; } @@ -59,7 +53,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const MWWorld::LiveCellRef *cellRef = mActor.get(); - if (toLower (info.mClass)!=toLower (cellRef->mBase->mClass)) + if ( Misc::StringUtils::lowerCase (info.mClass)!= Misc::StringUtils::lowerCase (cellRef->mBase->mClass)) return false; } @@ -70,7 +64,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const return false; MWMechanics::NpcStats& stats = MWWorld::Class::get (mActor).getNpcStats (mActor); - std::map::iterator iter = stats.getFactionRanks().find (toLower (info.mNpcFaction)); + std::map::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mNpcFaction)); if (iter==stats.getFactionRanks().end()) return false; @@ -99,7 +93,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const if (!info.mPcFaction.empty()) { MWMechanics::NpcStats& stats = MWWorld::Class::get (player).getNpcStats (player); - std::map::iterator iter = stats.getFactionRanks().find (toLower (info.mPcFaction)); + std::map::iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); if(iter==stats.getFactionRanks().end()) return false; @@ -111,7 +105,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const // check cell if (!info.mCell.empty()) - if (toLower (player.getCell()->mCell->mName) != toLower (info.mCell)) + if (Misc::StringUtils::lowerCase (player.getCell()->mCell->mName) != Misc::StringUtils::lowerCase (info.mCell)) return false; return true; @@ -127,6 +121,18 @@ bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const return true; } +bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info) const +{ + bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + + if (isCreature) + return true; + + int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); + + return actorDisposition >= info.mData.mDisposition; +} + bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && mActor.getTypeName()!=typeid (ESM::NPC).name()) @@ -168,7 +174,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c int i = 0; for (; i (script->mVarNames.size()); ++i) - if (script->mVarNames[i]==name) + if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name) break; if (i>=static_cast (script->mVarNames.size())) @@ -242,7 +248,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con std::string name = select.getName(); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (toLower(iter->getCellRef().mRefID) == name) + if (Misc::StringUtils::lowerCase(iter->getCellRef().mRefID) == name) sum += iter->getRefData().getCount(); return sum; @@ -283,7 +289,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_PcGender: - return player.get()->mBase->mFlags & ESM::NPC::Female ? 0 : 1; + return player.get()->mBase->isMale() ? 0 : 1; case SelectWrapper::Function_PcClothingModifier: { @@ -408,23 +414,23 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Id: - return select.getName()==toLower (MWWorld::Class::get (mActor).getId (mActor)); + return select.getName()==Misc::StringUtils::lowerCase (MWWorld::Class::get (mActor).getId (mActor)); case SelectWrapper::Function_Faction: - return toLower (mActor.get()->mBase->mFaction)==select.getName(); + return Misc::StringUtils::lowerCase (mActor.get()->mBase->mFaction)==select.getName(); case SelectWrapper::Function_Class: - return toLower (mActor.get()->mBase->mClass)==select.getName(); + return Misc::StringUtils::lowerCase (mActor.get()->mBase->mClass)==select.getName(); case SelectWrapper::Function_Race: - return toLower (mActor.get()->mBase->mRace)==select.getName(); + return Misc::StringUtils::lowerCase (mActor.get()->mBase->mRace)==select.getName(); case SelectWrapper::Function_Cell: - return toLower (mActor.getCell()->mCell->mName)==select.getName(); + return Misc::StringUtils::lowerCase (mActor.getCell()->mCell->mName)==select.getName(); case SelectWrapper::Function_SameGender: @@ -433,8 +439,8 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameRace: - return toLower (mActor.get()->mBase->mRace)!= - toLower (player.get()->mBase->mRace); + return Misc::StringUtils::lowerCase (mActor.get()->mBase->mRace)!= + Misc::StringUtils::lowerCase (player.get()->mBase->mRace); case SelectWrapper::Function_SameFaction: @@ -553,18 +559,69 @@ MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedTo : mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer) {} -bool MWDialogue::Filter::operator() (const ESM::DialInfo& info) const +const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const { - return testActor (info) && testPlayer (info) && testSelectStructs (info); + std::vector suitableInfos = list (dialogue, fallbackToInfoRefusal, false); + + if (suitableInfos.empty()) + return NULL; + else + return suitableInfos[0]; } -const ESM::DialInfo *MWDialogue::Filter::search (const ESM::Dialogue& dialogue) const +std::vector MWDialogue::Filter::list (const ESM::Dialogue& dialogue, + bool fallbackToInfoRefusal, bool searchAll) const +{ + std::vector infos; + + bool infoRefusal = false; + + // Iterate over topic responses to find a matching one + for (std::vector::const_iterator iter = dialogue.mInfo.begin(); + iter!=dialogue.mInfo.end(); ++iter) + { + if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) + { + if (testDisposition (*iter)) { + infos.push_back(&*iter); + if (!searchAll) + break; + } + else + infoRefusal = true; + } + } + + if (infos.empty() && infoRefusal && fallbackToInfoRefusal) + { + // No response is valid because of low NPC disposition, + // search a response in the topic "Info Refusal" + + const MWWorld::Store &dialogues = + MWBase::Environment::get().getWorld()->getStore().get(); + + const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal"); + + for (std::vector::const_iterator iter = infoRefusalDialogue.mInfo.begin(); + iter!=infoRefusalDialogue.mInfo.end(); ++iter) + if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter)) { + infos.push_back(&*iter); + if (!searchAll) + break; + } + } + + return infos; +} + +bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const { for (std::vector::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) - if ((*this) (*iter)) - return &*iter; + { + if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) + return true; + } - return 0; + return false; } - diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 7c8f1116f..069bf6353 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWDIALOGUE_FILTER_H #define GAME_MWDIALOGUE_FILTER_H +#include + #include "../mwworld/ptr.hpp" namespace ESM @@ -18,40 +20,48 @@ namespace MWDialogue MWWorld::Ptr mActor; int mChoice; bool mTalkedToPlayer; - + bool testActor (const ESM::DialInfo& info) const; ///< Is this the right actor for this \a info? - + bool testPlayer (const ESM::DialInfo& info) const; ///< Do the player and the cell the player is currently in match \a info? - + bool testSelectStructs (const ESM::DialInfo& info) const; ///< Are all select structs matching? - + + bool testDisposition (const ESM::DialInfo& info) const; + ///< Is the actor disposition toward the player high enough? + bool testSelectStruct (const SelectWrapper& select) const; - + bool testSelectStructNumeric (const SelectWrapper& select) const; - + int getSelectStructInteger (const SelectWrapper& select) const; - + bool getSelectStructBoolean (const SelectWrapper& select) const; - + int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const; - + bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const; bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const; - - public: - - Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); - bool operator() (const ESM::DialInfo& info) const; - ///< \return does the dialogue match? - - const ESM::DialInfo *search (const ESM::Dialogue& dialogue) const; + public: + + Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); + + std::vector list (const ESM::Dialogue& dialogue, + bool fallbackToInfoRefusal, bool searchAll) const; + + const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; + ///< Get a matching response for the requested dialogue. + /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. + + bool responseAvailable (const ESM::Dialogue& dialogue) const; + ///< Does a matching response exist? (disposition is ignored for this check) }; } diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index e6141884c..5ffde5499 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -61,8 +61,8 @@ namespace MWDialogue StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index) { int day = MWBase::Environment::get().getWorld()->getGlobalVariable ("dayspassed").mLong; - int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong; - int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong; + int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong; + int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong; return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth); } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 2b2c60381..5e2bc6bc0 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -31,6 +31,12 @@ namespace MWDialogue void Journal::addEntry (const std::string& id, int index) { + // bail out of we already have heard this... + std::string infoId = JournalEntry::idFromIndex (id, index); + for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i) + if (i->mTopic == id && i->mInfoId == infoId) + return; + StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index); mJournal.push_back (entry); diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 1462ee8ba..9d705f6be 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -8,18 +8,10 @@ #include #include +#include + namespace { - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - template bool selectCompareImp (char comp, T1 value1, T2 value2) { @@ -39,14 +31,13 @@ namespace template bool selectCompareImp (const ESM::DialInfo::SelectStruct& select, T value1) { - if (select.mType==ESM::VT_Short || select.mType==ESM::VT_Int || - select.mType==ESM::VT_Long) + if (select.mValue.getType()==ESM::VT_Int) { - return selectCompareImp (select.mSelectRule[4], value1, select.mI); + return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getInteger()); } - else if (select.mType==ESM::VT_Float) + else if (select.mValue.getType()==ESM::VT_Float) { - return selectCompareImp (select.mSelectRule[4], value1, select.mF); + return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getFloat()); } else throw std::runtime_error ( @@ -307,5 +298,5 @@ bool MWDialogue::SelectWrapper::selectCompare (bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return toLower (mSelect.mSelectRule.substr (5)); + return Misc::StringUtils::lowerCase (mSelect.mSelectRule.substr (5)); } diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index b6e7c07ae..3253b20d6 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -27,17 +27,17 @@ namespace MWDialogue mEntries.push_back (entry.mInfoId); } - Topic::TEntryIter Topic::begin() + Topic::TEntryIter Topic::begin() const { return mEntries.begin(); } - Topic::TEntryIter Topic::end() + Topic::TEntryIter Topic::end() const { return mEntries.end(); } - JournalEntry Topic::getEntry (const std::string& infoId) + JournalEntry Topic::getEntry (const std::string& infoId) const { return JournalEntry (mTopic, infoId); } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 566f60ab0..c3f0baabc 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -34,13 +34,15 @@ namespace MWDialogue /// /// \note Redundant entries are ignored. - TEntryIter begin(); + std::string const & getName () const { return mTopic; } + + TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. - TEntryIter end(); + TEntryIter end() const; ///< Iterator pointing past the end of the journal for this topic. - JournalEntry getEntry (const std::string& infoId); + JournalEntry getEntry (const std::string& infoId) const; }; } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index db1a81c2c..fce612600 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -237,7 +237,7 @@ namespace MWGui Widgets::SpellEffectList _list = Widgets::MWEffectList::effectListFromESM(&list); effectsWidget->setEffectList(_list); - std::vector effectItems; + std::vector effectItems; effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0); effectsWidget->setCoord(coord); } diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 4837821e0..4b07dd698 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -25,7 +25,7 @@ bool sortBirthSigns(const std::pair& left, c } BirthDialog::BirthDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_birth.layout", parWindowManager) + : WindowModal("openmw_chargen_birth.layout", parWindowManager) { // Centre dialog center(); @@ -40,15 +40,14 @@ BirthDialog::BirthDialog(MWBase::WindowManager& parWindowManager) mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked); - okButton->setEnabled(false); updateBirths(); updateSpells(); @@ -56,7 +55,7 @@ BirthDialog::BirthDialog(MWBase::WindowManager& parWindowManager) void BirthDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) @@ -67,7 +66,7 @@ void BirthDialog::setNextButtonShow(bool shown) void BirthDialog::open() { - WindowBase::open(); + WindowModal::open(); updateBirths(); updateSpells(); } @@ -83,9 +82,8 @@ void BirthDialog::setBirthId(const std::string &birthId) if (boost::iequals(*mBirthList->getItemDataAt(i), birthId)) { mBirthList->setIndexSelected(i); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); break; } } @@ -112,9 +110,8 @@ void BirthDialog::onSelectBirth(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); const std::string *birthId = mBirthList->getItemDataAt(_index); if (boost::iequals(mCurrentBirthId, *birthId)) @@ -133,8 +130,6 @@ void BirthDialog::updateBirths() const MWWorld::Store &signs = MWBase::Environment::get().getWorld()->getStore().get(); - int index = 0; - // sort by name std::vector < std::pair > birthSigns; @@ -145,18 +140,26 @@ void BirthDialog::updateBirths() } std::sort(birthSigns.begin(), birthSigns.end(), sortBirthSigns); - for (std::vector < std::pair >::const_iterator it2 = birthSigns.begin(); it2 != birthSigns.end(); ++it2) + int index = 0; + for (std::vector >::const_iterator it2 = birthSigns.begin(); + it2 != birthSigns.end(); ++it2, ++index) { mBirthList->addItem(it2->second->mName, it2->first); - if (boost::iequals(it2->first, mCurrentBirthId)) + if (mCurrentBirthId.empty()) + { mBirthList->setIndexSelected(index); - ++index; + mCurrentBirthId = it2->first; + } + else if (boost::iequals(it2->first, mCurrentBirthId)) + { + mBirthList->setIndexSelected(index); + } } } void BirthDialog::updateSpells() { - for (std::vector::iterator it = mSpellItems.begin(); it != mSpellItems.end(); ++it) + for (std::vector::iterator it = mSpellItems.begin(); it != mSpellItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index f16f92325..d3f82dace 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -10,7 +10,7 @@ namespace MWGui { - class BirthDialog : public WindowBase + class BirthDialog : public WindowModal { public: BirthDialog(MWBase::WindowManager& parWindowManager); @@ -46,9 +46,9 @@ namespace MWGui void updateSpells(); MyGUI::ListBox* mBirthList; - MyGUI::WidgetPtr mSpellArea; + MyGUI::Widget* mSpellArea; MyGUI::ImageBox* mBirthImage; - std::vector mSpellItems; + std::vector mSpellItems; std::string mCurrentBirthId; }; diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index bc3cd7b40..777751069 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -14,8 +14,10 @@ using namespace MWGui; -BookWindow::BookWindow (MWBase::WindowManager& parWindowManager) : - WindowBase("openmw_book.layout", parWindowManager) +BookWindow::BookWindow (MWBase::WindowManager& parWindowManager) + : WindowBase("openmw_book.layout", parWindowManager) + , mTakeButtonShow(true) + , mTakeButtonAllowed(true) { getWidget(mCloseButton, "CloseButton"); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onCloseButtonClicked); @@ -85,10 +87,17 @@ void BookWindow::open (MWWorld::Ptr book) void BookWindow::setTakeButtonShow(bool show) { - mTakeButton->setVisible(show); + mTakeButtonShow = show; + mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } -void BookWindow::onCloseButtonClicked (MyGUI::Widget* _sender) +void BookWindow::setInventoryAllowed(bool allowed) +{ + mTakeButtonAllowed = allowed; + mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); +} + +void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender) { // no 3d sounds because the object could be in a container. MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); @@ -96,7 +105,7 @@ void BookWindow::onCloseButtonClicked (MyGUI::Widget* _sender) mWindowManager.removeGuiMode(GM_Book); } -void BookWindow::onTakeButtonClicked (MyGUI::Widget* _sender) +void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender) { MWBase::Environment::get().getSoundManager()->playSound ("Item Book Up", 1.0, 1.0, MWBase::SoundManager::Play_NoTrack); @@ -106,7 +115,7 @@ void BookWindow::onTakeButtonClicked (MyGUI::Widget* _sender) mWindowManager.removeGuiMode(GM_Book); } -void BookWindow::onNextPageButtonClicked (MyGUI::Widget* _sender) +void BookWindow::onNextPageButtonClicked (MyGUI::Widget* sender) { if ((mCurrentPage+1)*2 < mPages.size()) { @@ -118,7 +127,7 @@ void BookWindow::onNextPageButtonClicked (MyGUI::Widget* _sender) } } -void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* _sender) +void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* sender) { if (mCurrentPage > 0) { diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index fedb783b2..a509f131f 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -5,6 +5,8 @@ #include "../mwworld/ptr.hpp" +#include "imagebutton.hpp" + namespace MWGui { class BookWindow : public WindowBase @@ -15,20 +17,22 @@ namespace MWGui void open(MWWorld::Ptr book); void setTakeButtonShow(bool show); + void setInventoryAllowed(bool allowed); + protected: - void onNextPageButtonClicked (MyGUI::Widget* _sender); - void onPrevPageButtonClicked (MyGUI::Widget* _sender); - void onCloseButtonClicked (MyGUI::Widget* _sender); - void onTakeButtonClicked (MyGUI::Widget* _sender); + void onNextPageButtonClicked (MyGUI::Widget* sender); + void onPrevPageButtonClicked (MyGUI::Widget* sender); + void onCloseButtonClicked (MyGUI::Widget* sender); + void onTakeButtonClicked (MyGUI::Widget* sender); void updatePages(); void clearPages(); private: - MyGUI::Button* mCloseButton; - MyGUI::Button* mTakeButton; - MyGUI::Button* mNextPageButton; - MyGUI::Button* mPrevPageButton; + MWGui::ImageButton* mCloseButton; + MWGui::ImageButton* mTakeButton; + MWGui::ImageButton* mNextPageButton; + MWGui::ImageButton* mPrevPageButton; MyGUI::TextBox* mLeftPageNumber; MyGUI::TextBox* mRightPageNumber; MyGUI::Widget* mLeftPage; @@ -38,6 +42,9 @@ namespace MWGui std::vector mPages; MWWorld::Ptr mBook; + + bool mTakeButtonShow; + bool mTakeButtonAllowed; }; } diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index c14c7f74b..a2f09096a 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -21,7 +21,7 @@ using namespace MWGui; /* GenerateClassResultDialog */ GenerateClassResultDialog::GenerateClassResultDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_generate_class_result.layout", parWindowManager) + : WindowModal("openmw_chargen_generate_class_result.layout", parWindowManager) { // Centre dialog center(); @@ -31,11 +31,11 @@ GenerateClassResultDialog::GenerateClassResultDialog(MWBase::WindowManager& parW getWidget(mClassImage, "ClassImage"); getWidget(mClassName, "ClassName"); - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); @@ -68,7 +68,7 @@ void GenerateClassResultDialog::onBackClicked(MyGUI::Widget* _sender) /* PickClassDialog */ PickClassDialog::PickClassDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_class.layout", parWindowManager) + : WindowModal("openmw_chargen_class.layout", parWindowManager) { // Centre dialog center(); @@ -97,14 +97,13 @@ PickClassDialog::PickClassDialog(MWBase::WindowManager& parWindowManager) getWidget(mClassImage, "ClassImage"); - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onOkClicked); - okButton->setEnabled(false); updateClasses(); updateStats(); @@ -112,7 +111,7 @@ PickClassDialog::PickClassDialog(MWBase::WindowManager& parWindowManager) void PickClassDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) @@ -123,6 +122,7 @@ void PickClassDialog::setNextButtonShow(bool shown) void PickClassDialog::open() { + WindowModal::open (); updateClasses(); updateStats(); } @@ -138,9 +138,8 @@ void PickClassDialog::setClassId(const std::string &classId) if (boost::iequals(*mClassList->getItemDataAt(i), classId)) { mClassList->setIndexSelected(i); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); break; } } @@ -167,9 +166,8 @@ void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); const std::string *classId = mClassList->getItemDataAt(_index); if (boost::iequals(mCurrentClassId, *classId)) @@ -197,8 +195,15 @@ void PickClassDialog::updateClasses() const std::string &id = it->mId; mClassList->addItem(it->mName, id); - if (boost::iequals(id, mCurrentClassId)) + if (mCurrentClassId.empty()) + { + mCurrentClassId = id; mClassList->setIndexSelected(index); + } + else if (boost::iequals(id, mCurrentClassId)) + { + mClassList->setIndexSelected(index); + } ++index; } } @@ -251,7 +256,7 @@ void InfoBoxDialog::fitToText(MyGUI::TextBox* widget) widget->setSize(size); } -void InfoBoxDialog::layoutVertically(MyGUI::WidgetPtr widget, int margin) +void InfoBoxDialog::layoutVertically(MyGUI::Widget* widget, int margin) { size_t count = widget->getChildCount(); int pos = 0; @@ -259,7 +264,7 @@ void InfoBoxDialog::layoutVertically(MyGUI::WidgetPtr widget, int margin) int width = 0; for (unsigned i = 0; i < count; ++i) { - MyGUI::WidgetPtr child = widget->getChildAt(i); + MyGUI::Widget* child = widget->getChildAt(i); if (!child->getVisible()) continue; @@ -272,7 +277,7 @@ void InfoBoxDialog::layoutVertically(MyGUI::WidgetPtr widget, int margin) } InfoBoxDialog::InfoBoxDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_infobox.layout", parWindowManager) + : WindowModal("openmw_infobox.layout", parWindowManager) , mCurrentButton(-1) { getWidget(mTextBox, "TextBox"); @@ -297,7 +302,7 @@ std::string InfoBoxDialog::getText() const void InfoBoxDialog::setButtons(ButtonList &buttons) { - for (std::vector::iterator it = this->mButtons.begin(); it != this->mButtons.end(); ++it) + for (std::vector::iterator it = this->mButtons.begin(); it != this->mButtons.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } @@ -305,7 +310,7 @@ void InfoBoxDialog::setButtons(ButtonList &buttons) mCurrentButton = -1; // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget - MyGUI::ButtonPtr button; + MyGUI::Button* button; MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10); ButtonList::const_iterator end = buttons.end(); for (ButtonList::const_iterator it = buttons.begin(); it != end; ++it) @@ -323,6 +328,7 @@ void InfoBoxDialog::setButtons(ButtonList &buttons) void InfoBoxDialog::open() { + WindowModal::open(); // Fix layout layoutVertically(mTextBox, 4); layoutVertically(mButtonBar, 6); @@ -336,11 +342,11 @@ int InfoBoxDialog::getChosenButton() const return mCurrentButton; } -void InfoBoxDialog::onButtonClicked(MyGUI::WidgetPtr _sender) +void InfoBoxDialog::onButtonClicked(MyGUI::Widget* _sender) { - std::vector::const_iterator end = mButtons.end(); + std::vector::const_iterator end = mButtons.end(); int i = 0; - for (std::vector::const_iterator it = mButtons.begin(); it != end; ++it) + for (std::vector::const_iterator it = mButtons.begin(); it != end; ++it) { if (*it == _sender) { @@ -369,11 +375,11 @@ ClassChoiceDialog::ClassChoiceDialog(MWBase::WindowManager& parWindowManager) /* CreateClassDialog */ CreateClassDialog::CreateClassDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_create_class.layout", parWindowManager) - , mSpecDialog(nullptr) - , mAttribDialog(nullptr) - , mSkillDialog(nullptr) - , mDescDialog(nullptr) + : WindowModal("openmw_chargen_create_class.layout", parWindowManager) + , mSpecDialog(NULL) + , mAttribDialog(NULL) + , mSkillDialog(NULL) + , mDescDialog(NULL) { // Centre dialog center(); @@ -414,15 +420,15 @@ CreateClassDialog::CreateClassDialog(MWBase::WindowManager& parWindowManager) // Make sure the edit box has focus MyGUI::InputManager::getInstance().setKeyFocusWidget(mEditName); - MyGUI::ButtonPtr descriptionButton; + MyGUI::Button* descriptionButton; getWidget(descriptionButton, "DescriptionButton"); descriptionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionClicked); - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onOkClicked); @@ -512,7 +518,7 @@ std::vector CreateClassDialog::getMinorSkills() const void CreateClassDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) @@ -538,7 +544,7 @@ void CreateClassDialog::onDialogCancel() mDescDialog = 0; } -void CreateClassDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) +void CreateClassDialog::onSpecializationClicked(MyGUI::Widget* _sender) { delete mSpecDialog; mSpecDialog = new SelectSpecializationDialog(mWindowManager); @@ -688,7 +694,7 @@ SelectSpecializationDialog::SelectSpecializationDialog(MWBase::WindowManager& pa ToolTips::createSpecializationToolTip(mSpecialization1, magic, ESM::Class::Magic); ToolTips::createSpecializationToolTip(mSpecialization2, stealth, ESM::Class::Stealth); - MyGUI::ButtonPtr cancelButton; + MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked); @@ -700,7 +706,7 @@ SelectSpecializationDialog::~SelectSpecializationDialog() // widget controls -void SelectSpecializationDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) +void SelectSpecializationDialog::onSpecializationClicked(MyGUI::Widget* _sender) { if (_sender == mSpecialization0) mSpecializationId = ESM::Class::Combat; @@ -741,7 +747,7 @@ SelectAttributeDialog::SelectAttributeDialog(MWBase::WindowManager& parWindowMan ToolTips::createAttributeToolTip(attribute, attribute->getAttributeId()); } - MyGUI::ButtonPtr cancelButton; + MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked); @@ -834,7 +840,7 @@ SelectSkillDialog::SelectSkillDialog(MWBase::WindowManager& parWindowManager) } } - MyGUI::ButtonPtr cancelButton; + MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked); @@ -867,7 +873,7 @@ DescriptionDialog::DescriptionDialog(MWBase::WindowManager& parWindowManager) getWidget(mTextEdit, "TextEdit"); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked); okButton->setCaption(mWindowManager.getGameSettingString("sInputMenu1", "")); diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index c7699b308..8c60331d8 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_CLASS_H #define MWGUI_CLASS_H -#include + #include "widgets.hpp" #include "window_base.hpp" @@ -12,7 +12,7 @@ namespace MWGui { - class InfoBoxDialog : public WindowBase + class InfoBoxDialog : public WindowModal { public: InfoBoxDialog(MWBase::WindowManager& parWindowManager); @@ -35,17 +35,17 @@ namespace MWGui EventHandle_Int eventButtonSelected; protected: - void onButtonClicked(MyGUI::WidgetPtr _sender); + void onButtonClicked(MyGUI::Widget* _sender); private: void fitToText(MyGUI::TextBox* widget); - void layoutVertically(MyGUI::WidgetPtr widget, int margin); + void layoutVertically(MyGUI::Widget* widget, int margin); int mCurrentButton; - MyGUI::WidgetPtr mTextBox; + MyGUI::Widget* mTextBox; MyGUI::TextBox* mText; - MyGUI::WidgetPtr mButtonBar; - std::vector mButtons; + MyGUI::Widget* mButtonBar; + std::vector mButtons; }; // Lets the player choose between 3 ways of creating a class @@ -63,7 +63,7 @@ namespace MWGui ClassChoiceDialog(MWBase::WindowManager& parWindowManager); }; - class GenerateClassResultDialog : public WindowBase + class GenerateClassResultDialog : public WindowModal { public: GenerateClassResultDialog(MWBase::WindowManager& parWindowManager); @@ -90,7 +90,7 @@ namespace MWGui std::string mCurrentClassId; }; - class PickClassDialog : public WindowBase + class PickClassDialog : public WindowModal { public: PickClassDialog(MWBase::WindowManager& parWindowManager); @@ -235,10 +235,10 @@ namespace MWGui void onOkClicked(MyGUI::Widget* _sender); private: - MyGUI::EditPtr mTextEdit; + MyGUI::EditBox* mTextEdit; }; - class CreateClassDialog : public WindowBase + class CreateClassDialog : public WindowModal { public: CreateClassDialog(MWBase::WindowManager& parWindowManager); @@ -265,7 +265,7 @@ namespace MWGui void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); - void onSpecializationClicked(MyGUI::WidgetPtr _sender); + void onSpecializationClicked(MyGUI::Widget* _sender); void onSpecializationSelected(); void onAttributeClicked(Widgets::MWAttributePtr _sender); void onAttributeSelected(); @@ -280,7 +280,7 @@ namespace MWGui void update(); private: - MyGUI::EditPtr mEditName; + MyGUI::EditBox* mEditName; MyGUI::TextBox* mSpecializationName; Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; Widgets::MWSkillPtr mMajorSkill[5]; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b2281d87e..1aebe57da 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -216,7 +216,7 @@ namespace MWGui } } - void Console::keyPress(MyGUI::WidgetPtr _sender, + void Console::keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) { @@ -266,7 +266,7 @@ namespace MWGui } } - void Console::acceptCommand(MyGUI::EditPtr _sender) + void Console::acceptCommand(MyGUI::EditBox* _sender) { const std::string &cm = command->getCaption(); if(cm.empty()) return; diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 1893b0148..b1d961ed2 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -55,8 +55,8 @@ namespace MWGui public: - MyGUI::EditPtr command; - MyGUI::EditPtr history; + MyGUI::EditBox* command; + MyGUI::EditBox* history; typedef std::list StringList; @@ -95,11 +95,11 @@ namespace MWGui private: - void keyPress(MyGUI::WidgetPtr _sender, + void keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char); - void acceptCommand(MyGUI::EditPtr _sender); + void acceptCommand(MyGUI::EditBox* _sender); std::string complete( std::string input, std::vector &matches ); }; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 20bc95445..2b8000312 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -15,6 +15,7 @@ #include "../mwworld/manualref.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -70,9 +71,11 @@ namespace } -ContainerBase::ContainerBase(DragAndDrop* dragAndDrop) : - mDragAndDrop(dragAndDrop), - mFilter(ContainerBase::Filter_All) +ContainerBase::ContainerBase(DragAndDrop* dragAndDrop) + : mDragAndDrop(dragAndDrop) + , mFilter(ContainerBase::Filter_All) + , mDisplayEquippedItems(true) + , mHighlightEquippedItems(true) { } @@ -195,13 +198,13 @@ void ContainerBase::sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count) if (isInventory()) { MWBase::Environment::get().getWindowManager()->getTradeWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, true); MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); } else { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, true); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); } @@ -218,13 +221,13 @@ void ContainerBase::sellItem(MyGUI::Widget* _sender, int count) if (isInventory()) { MWBase::Environment::get().getWindowManager()->getTradeWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, false); MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); } else { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, false); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); } @@ -313,7 +316,6 @@ void ContainerBase::onContainerClicked(MyGUI::Widget* _sender) { object.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); } - std::cout << "container weight " << curWeight << "/" << capacity << std::endl; } else { @@ -430,7 +432,7 @@ void ContainerBase::drawItems() equippedItems.erase(found); } // and add the items that are left (= have the correct category) - if (!ignoreEquippedItems()) + if (mDisplayEquippedItems && mHighlightEquippedItems) { for (std::vector::const_iterator it=equippedItems.begin(); it != equippedItems.end(); ++it) @@ -445,7 +447,8 @@ void ContainerBase::drawItems() std::vector regularItems; for (MWWorld::ContainerStoreIterator iter (containerStore.begin(categories)); iter!=containerStore.end(); ++iter) { - if (std::find(equippedItems.begin(), equippedItems.end(), *iter) == equippedItems.end() + if ( (std::find(equippedItems.begin(), equippedItems.end(), *iter) == equippedItems.end() + || (!mHighlightEquippedItems && mDisplayEquippedItems)) && std::find(ignoreItems.begin(), ignoreItems.end(), *iter) == ignoreItems.end() && std::find(mBoughtItems.begin(), mBoughtItems.end(), *iter) == mBoughtItems.end()) regularItems.push_back(*iter); @@ -587,6 +590,27 @@ void ContainerBase::returnBoughtItems(MWWorld::ContainerStore& store) } } +std::vector ContainerBase::getEquippedItems() +{ + if (mPtr.getTypeName() != typeid(ESM::NPC).name()) + return std::vector(); + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + + std::vector items; + + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end()) + { + items.push_back(*it); + } + } + + return items; +} + MWWorld::ContainerStore& ContainerBase::getContainerStore() { MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); @@ -599,6 +623,7 @@ ContainerWindow::ContainerWindow(MWBase::WindowManager& parWindowManager,DragAnd : ContainerBase(dragAndDrop) , WindowBase("openmw_container_window.layout", parWindowManager) { + getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mTakeButton, "TakeButton"); getWidget(mCloseButton, "CloseButton"); @@ -608,6 +633,7 @@ ContainerWindow::ContainerWindow(MWBase::WindowManager& parWindowManager,DragAnd getWidget(itemView, "ItemView"); setWidgets(containerWidget, itemView); + mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); @@ -625,8 +651,18 @@ void ContainerWindow::onWindowResize(MyGUI::Window* window) drawItems(); } -void ContainerWindow::open(MWWorld::Ptr container) +void ContainerWindow::open(MWWorld::Ptr container, bool loot) { + mDisplayEquippedItems = true; + mHighlightEquippedItems = false; + if (container.getTypeName() == typeid(ESM::NPC).name() && !loot) + { + // we are stealing stuff + mDisplayEquippedItems = false; + } + + mDisposeCorpseButton->setVisible(loot); + openContainer(container); setTitle(MWWorld::Class::get(container).getName(container)); drawItems(); @@ -671,6 +707,22 @@ void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) } } +void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) +{ + if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) + { + onTakeAllButtonClicked(mTakeButton); + + /// \todo I don't think this is the correct flag to check + if (MWWorld::Class::get(mPtr).isEssential(mPtr)) + mWindowManager.messageBox("#{sDisposeCorpseFail}"); + else + MWBase::Environment::get().getWorld()->deleteObject(mPtr); + + mPtr = MWWorld::Ptr(); + } +} + void ContainerWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 08d425032..3c8127b26 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -82,6 +82,9 @@ namespace MWGui void drawItems(); protected: + bool mDisplayEquippedItems; + bool mHighlightEquippedItems; + MyGUI::ScrollView* mItemView; MyGUI::Widget* mContainerWidget; @@ -111,14 +114,13 @@ namespace MWGui virtual bool isTradeWindow() { return false; } virtual bool isInventory() { return false; } - virtual std::vector getEquippedItems() { return std::vector(); } + virtual std::vector getEquippedItems(); virtual void _unequipItem(MWWorld::Ptr item) { ; } virtual bool isTrading() { return false; } virtual void onSelectedItemImpl(MWWorld::Ptr item) { ; } - virtual bool ignoreEquippedItems() { return false; } virtual std::vector itemsToIgnore() { return std::vector(); } virtual void notifyContentChanged() { ; } @@ -131,15 +133,17 @@ namespace MWGui virtual ~ContainerWindow(); - void open(MWWorld::Ptr container); + void open(MWWorld::Ptr container, bool loot=false); protected: + MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; MyGUI::Button* mCloseButton; void onWindowResize(MyGUI::Window* window); void onCloseButtonClicked(MyGUI::Widget* _sender); void onTakeAllButtonClicked(MyGUI::Widget* _sender); + void onDisposeCorpseButtonClicked(MyGUI::Widget* sender); virtual void onReferenceUnavailable(); }; diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp new file mode 100644 index 000000000..b0d164bed --- /dev/null +++ b/apps/openmw/mwgui/cursor.cpp @@ -0,0 +1,131 @@ +#include "cursor.hpp" + +#include +#include +#include +#include +#include + +#include + + +namespace MWGui +{ + + + ResourceImageSetPointerFix::ResourceImageSetPointerFix() + : mImageSet(NULL) + , mRotation(0) + { + } + + ResourceImageSetPointerFix::~ResourceImageSetPointerFix() + { + } + + void ResourceImageSetPointerFix::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) + { + Base::deserialization(_node, _version); + + MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); + while (info.next("Property")) + { + const std::string& key = info->findAttribute("key"); + const std::string& value = info->findAttribute("value"); + + if (key == "Point") + mPoint = MyGUI::IntPoint::parse(value); + else if (key == "Size") + mSize = MyGUI::IntSize::parse(value); + else if (key == "Rotation") + mRotation = MyGUI::utility::parseInt(value); + else if (key == "Resource") + mImageSet = MyGUI::ResourceManager::getInstance().getByName(value)->castType(); + } + } + + int ResourceImageSetPointerFix::getRotation() + { + return mRotation; + } + + void ResourceImageSetPointerFix::setImage(MyGUI::ImageBox* _image) + { + if (mImageSet != NULL) + _image->setItemResourceInfo(mImageSet->getIndexInfo(0, 0)); + } + + void ResourceImageSetPointerFix::setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point) + { + _image->setCoord(_point.left - mPoint.left, _point.top - mPoint.top, mSize.width, mSize.height); + } + + MyGUI::ResourceImageSetPtr ResourceImageSetPointerFix:: getImageSet() + { + return mImageSet; + } + + MyGUI::IntPoint ResourceImageSetPointerFix::getHotSpot() + { + return mPoint; + } + + MyGUI::IntSize ResourceImageSetPointerFix::getSize() + { + return mSize; + } + + // ---------------------------------------------------------------------------------------- + + Cursor::Cursor() + { + // hide mygui's pointer since we're rendering it ourselves (because mygui's pointer doesn't support rotation) + MyGUI::PointerManager::getInstance().setVisible(false); + + MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &Cursor::onCursorChange); + + mWidget = MyGUI::Gui::getInstance().createWidget("RotatingSkin",0,0,0,0,MyGUI::Align::Default,"Pointer",""); + + onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); + } + + Cursor::~Cursor() + { + } + + void Cursor::onCursorChange(const std::string &name) + { + ResourceImageSetPointerFix* imgSetPtr = dynamic_cast( + MyGUI::PointerManager::getInstance().getByName(name)); + assert(imgSetPtr != NULL); + + MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet(); + + std::string texture = imgSet->getIndexInfo(0,0).texture; + + mSize = imgSetPtr->getSize(); + mHotSpot = imgSetPtr->getHotSpot(); + + int rotation = imgSetPtr->getRotation(); + + mWidget->setImageTexture(texture); + MyGUI::ISubWidget* main = mWidget->getSubWidgetMain(); + MyGUI::RotatingSkin* rotatingSubskin = main->castType(); + rotatingSubskin->setCenter(MyGUI::IntPoint(mSize.width/2,mSize.height/2)); + rotatingSubskin->setAngle(Ogre::Degree(rotation).valueRadians()); + } + + void Cursor::update() + { + MyGUI::IntPoint position = MyGUI::InputManager::getInstance().getMousePosition(); + + mWidget->setPosition(position - mHotSpot); + mWidget->setSize(mSize); + } + + void Cursor::setVisible(bool visible) + { + mWidget->setVisible(visible); + } + +} diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp new file mode 100644 index 000000000..3a4a05f4c --- /dev/null +++ b/apps/openmw/mwgui/cursor.hpp @@ -0,0 +1,62 @@ +#ifndef MWGUI_CURSOR_H +#define MWGUI_CURSOR_H + +#include +#include +#include + +namespace MWGui +{ + + /// \brief Allows us to get the members of + /// ResourceImageSetPointer that we need. + /// \example MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); + /// MyGUI::ResourceManager::getInstance().load("core.xml"); + class ResourceImageSetPointerFix : + public MyGUI::IPointer + { + MYGUI_RTTI_DERIVED( ResourceImageSetPointerFix ) + + public: + ResourceImageSetPointerFix(); + virtual ~ResourceImageSetPointerFix(); + + virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + + virtual void setImage(MyGUI::ImageBox* _image); + virtual void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point); + + //and now for the whole point of this class, allow us to get + //the hot spot, the image and the size of the cursor. + virtual MyGUI::ResourceImageSetPtr getImageSet(); + virtual MyGUI::IntPoint getHotSpot(); + virtual MyGUI::IntSize getSize(); + virtual int getRotation(); + + private: + MyGUI::IntPoint mPoint; + MyGUI::IntSize mSize; + MyGUI::ResourceImageSetPtr mImageSet; + int mRotation; // rotation in degrees + }; + + class Cursor + { + public: + Cursor(); + ~Cursor(); + void update (); + + void setVisible (bool visible); + + void onCursorChange (const std::string& name); + + private: + MyGUI::ImageBox* mWidget; + + MyGUI::IntSize mSize; + MyGUI::IntPoint mHotSpot; + }; +} + +#endif diff --git a/apps/openmw/mwgui/cursorreplace.cpp b/apps/openmw/mwgui/cursorreplace.cpp deleted file mode 100644 index a4b6a100b..000000000 --- a/apps/openmw/mwgui/cursorreplace.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "cursorreplace.hpp" - -#include -#include -#include - -#include -#include - -using namespace MWGui; - -CursorReplace::CursorReplace() -{ - OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_vresize.png", 90); - OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize1.png", -45); - OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize2.png", 45); - - OEngine::Render::Atlas::createFromFile("atlas1.cfg", "mwgui1", "textures\\"); - OEngine::Render::Atlas::createFromFile("mainmenu.cfg", "mwgui2", "textures\\"); -} diff --git a/apps/openmw/mwgui/cursorreplace.hpp b/apps/openmw/mwgui/cursorreplace.hpp deleted file mode 100644 index 06fe28e39..000000000 --- a/apps/openmw/mwgui/cursorreplace.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef GAME_CURSORREPLACE_H -#define GAME_CURSORREPLACE_H - -#include - -namespace MWGui -{ - /// \brief MyGUI does not support rotating cursors, so we have to do it manually - class CursorReplace - { - public: - CursorReplace(); - }; -} - -#endif diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 258e9174c..859e3008c 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -16,6 +16,8 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwdialogue/dialoguemanagerimp.hpp" + #include "dialogue_history.hpp" #include "widgets.hpp" #include "list.hpp" @@ -35,10 +37,7 @@ namespace { std::string lower_string(const std::string& str) { - std::string lowerCase; - - std::transform (str.begin(), str.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + std::string lowerCase = Misc::StringUtils::lowerCase (str); return lowerCase; } @@ -52,7 +51,6 @@ bool sortByLength (const std::string& left, const std::string& right) { return left.size() > right.size(); } - } @@ -153,7 +151,7 @@ DialogueWindow::DialogueWindow(MWBase::WindowManager& parWindowManager) getWidget(mTopicsList, "TopicsList"); mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); - MyGUI::ButtonPtr byeButton; + MyGUI::Button* byeButton; getWidget(byeButton, "ByeButton"); byeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); @@ -166,7 +164,7 @@ DialogueWindow::DialogueWindow(MWBase::WindowManager& parWindowManager) void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) { MyGUI::ISubWidgetText* t = mHistory->getClient()->getSubWidgetText(); - if(t == nullptr) + if(t == NULL) return; const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); @@ -180,9 +178,30 @@ void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) if(color != "#B29154") { MyGUI::UString key = mHistory->getColorTextAt(cursorPosition); - if(color == "#686EBA") MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); - if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); + if(color == "#686EBA") + { + std::map::iterator i = mHyperLinks.upper_bound(cursorPosition); + if( !mHyperLinks.empty() ) + { + --i; + + if( i->first + i->second.mLength > cursorPosition) + { + MWBase::Environment::get().getDialogueManager()->keywordSelected(i->second.mTrueValue); + } + } + else + { + // the link was colored, but it is not in mHyperLinks. + // It means that those liunks are not marked with @# and found + // by topic name search + MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); + } + } + + if(color == "#572D21") + MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); } } @@ -258,6 +277,7 @@ void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) setTitle(npcName); mTopicsList->clear(); + mHyperLinks.clear(); mHistory->setCaption(""); updateOptions(); } @@ -340,7 +360,7 @@ void addColorInString(std::string& str, const std::string& keyword,std::string c } } -std::string DialogueWindow::parseText(std::string text) +std::string DialogueWindow::parseText(const std::string& text) { bool separatorReached = false; // only parse topics that are below the separator (this prevents actions like "Barter" that are not topics from getting blue-colored) @@ -358,11 +378,56 @@ std::string DialogueWindow::parseText(std::string text) // sort by length to make sure longer topics are replaced first std::sort(topics.begin(), topics.end(), sortByLength); - for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + std::vector hypertext = MWDialogue::ParseHyperText(text); + + size_t historySize = 0; + if(mHistory->getClient()->getSubWidgetText() != NULL) { - addColorInString(text,*it,"#686EBA","#B29154"); + historySize = mHistory->getOnlyText().size(); } - return text; + + std::string result; + size_t hypertextPos = 0; + for (size_t i = 0; i < hypertext.size(); ++i) + { + if (hypertext[i].mLink) + { + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + std::string standardForm = hypertext[i].mText; + for(; asterisk_count > 0; --asterisk_count) + standardForm.append("*"); + + standardForm = + MWBase::Environment::get().getWindowManager()-> + getTranslationDataStorage().topicStandardForm(standardForm); + + if( std::find(topics.begin(), topics.end(), std::string(standardForm) ) != topics.end() ) + { + result.append("#686EBA").append(hypertext[i].mText).append("#B29154"); + + mHyperLinks[historySize+hypertextPos].mLength = MyGUI::UString(hypertext[i].mText).length(); + mHyperLinks[historySize+hypertextPos].mTrueValue = lower_string(standardForm); + } + else + result += hypertext[i].mText; + } + else + { + if( !mWindowManager.getTranslationDataStorage().hasTranslation() ) + { + for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + { + addColorInString(hypertext[i].mText, *it, "#686EBA", "#B29154"); + } + } + + result += hypertext[i].mText; + } + + hypertextPos += MyGUI::UString(hypertext[i].mText).length(); + } + + return result; } void DialogueWindow::addText(std::string text) @@ -370,6 +435,11 @@ void DialogueWindow::addText(std::string text) mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); } +void DialogueWindow::addMessageBox(const std::string& text) +{ + mHistory->addDialogText("\n#FFFFFF"+text+"#B29154"); +} + void DialogueWindow::addTitle(std::string text) { // This is called from the dialogue manager, so text is @@ -394,6 +464,7 @@ void DialogueWindow::updateOptions() { //Clear the list of topics mTopicsList->clear(); + mHyperLinks.clear(); mHistory->eraseText(0, mHistory->getTextLength()); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) @@ -419,7 +490,7 @@ void DialogueWindow::onReferenceUnavailable() void DialogueWindow::onFrame() { - if(mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name()) + if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name()) { int disp = std::max(0, std::min(100, MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 082d92524..e39cecc3c 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -65,6 +65,7 @@ namespace MWGui void setKeywords(std::list keyWord); void removeKeyword(std::string keyWord); void addText(std::string text); + void addMessageBox(const std::string& text); void addTitle(std::string text); void askQuestion(std::string question); void goodbye(); @@ -92,12 +93,18 @@ namespace MWGui virtual void onReferenceUnavailable(); + struct HyperLink + { + size_t mLength; + std::string mTrueValue; + }; + private: void updateOptions(); /** *Helper function that add topic keyword in blue in a text. */ - std::string parseText(std::string text); + std::string parseText(const std::string& text); int mServices; @@ -106,9 +113,11 @@ namespace MWGui DialogueHistory* mHistory; Widgets::MWList* mTopicsList; MyGUI::ProgressPtr mDispositionBar; - MyGUI::EditPtr mDispositionText; + MyGUI::EditBox* mDispositionText; PersuasionDialog mPersuasionDialog; + + std::map mHyperLinks; }; } #endif diff --git a/apps/openmw/mwgui/exposedwindow.cpp b/apps/openmw/mwgui/exposedwindow.cpp new file mode 100644 index 000000000..150a8c893 --- /dev/null +++ b/apps/openmw/mwgui/exposedwindow.cpp @@ -0,0 +1,24 @@ +#include "exposedwindow.hpp" + +namespace MWGui +{ + MyGUI::VectorWidgetPtr ExposedWindow::getSkinWidgetsByName (const std::string &name) + { + return MyGUI::Widget::getSkinWidgetsByName (name); + } + + MyGUI::Widget* ExposedWindow::getSkinWidget(const std::string & _name, bool _throw) + { + MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName (_name); + + if (widgets.empty()) + { + MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'"); + return NULL; + } + else + { + return widgets[0]; + } + } +} diff --git a/apps/openmw/mwgui/exposedwindow.hpp b/apps/openmw/mwgui/exposedwindow.hpp new file mode 100644 index 000000000..7df2fcb35 --- /dev/null +++ b/apps/openmw/mwgui/exposedwindow.hpp @@ -0,0 +1,26 @@ +#ifndef MWGUI_EXPOSEDWINDOW_H +#define MWGUI_EXPOSEDWINDOW_H + +#include + +namespace MWGui +{ + + /** + * @brief subclass to provide access to some Widget internals. + */ + class ExposedWindow : public MyGUI::Window + { + MYGUI_RTTI_DERIVED(ExposedWindow) + + public: + MyGUI::VectorWidgetPtr getSkinWidgetsByName (const std::string &name); + + MyGUI::Widget* getSkinWidget(const std::string & _name, bool _throw = true); + ///< Get a widget defined in the inner skin of this window. + }; + +} + +#endif + diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 53c23c25d..7f28e9e17 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,7 +1,14 @@ #include "formatting.hpp" +#include + +#include "../mwscript/interpretercontext.hpp" +#include "../mwworld/ptr.hpp" + #include +#include #include +#include using namespace MWGui; @@ -62,111 +69,135 @@ namespace return value; } + + Ogre::UTFString::unicode_char unicodeCharFromChar(char ch) + { + std::string s; + s += ch; + Ogre::UTFString string(s); + return string.getChar(0); + } } -std::vector BookTextParser::split(std::string text, const int width, const int height) +std::vector BookTextParser::split(std::string utf8Text, const int width, const int height) { + using Ogre::UTFString; std::vector result; - boost::algorithm::replace_all(text, "
", "\n"); - boost::algorithm::replace_all(text, "

", "\n\n"); + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext); + boost::algorithm::replace_all(utf8Text, "\n", ""); + boost::algorithm::replace_all(utf8Text, "
", "\n"); + boost::algorithm::replace_all(utf8Text, "

", "\n\n"); + + UTFString text(utf8Text); const int spacing = 48; - while (text.size() > 0) + const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<'); + const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\n'); + const UTFString::unicode_char SPACE = unicodeCharFromChar(' '); + + while (!text.empty()) { // read in characters until we have exceeded the size, or run out of text int currentWidth = 0; int currentHeight = 0; - std::string currentText; - std::string currentWord; - unsigned int i=0; - while (currentHeight <= height-spacing && i', i) == std::string::npos) + const size_t tagStart = index + 1; + const size_t tagEnd = text.find('>', tagStart); + if (tagEnd == UTFString::npos) throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + const std::string tag = text.substr(tagStart, tagEnd - tagStart).asUTF8(); - if (text.size() > i+4 && text.substr(i, 4) == "', i)-i), false); - currentHeight += (mHeight-h); + const int h = mHeight; + parseImage(tag, false); + currentHeight += (mHeight - h); currentWidth = 0; } - else if (text.size() > i+5 && text.substr(i, 5) == "', i)-i)); - currentHeight += 18; // keep this in sync with the font size + parseFont(tag); + if (currentWidth != 0) { + currentHeight += currentFontHeight(); + currentWidth = 0; + } currentWidth = 0; } - else if (text.size() > i+4 && text.substr(i, 4) == "', i)-i)); - currentHeight += 18; // keep this in sync with the font size - currentWidth = 0; + parseDiv(tag); + if (currentWidth != 0) { + currentHeight += currentFontHeight(); + currentWidth = 0; + } } - - currentText += text.substr(i, text.find('>', i)-i+1); - i = text.find('>', i); + index = tagEnd; } - else if (text[i] == '\n') + else if (ch == NEWLINE) { - currentHeight += 18; // keep this in sync with the font size + currentHeight += currentFontHeight(); currentWidth = 0; - currentWord = ""; - currentText += text[i]; + currentWordStart = index; } - else if (text[i] == ' ') + else if (ch == SPACE) { currentWidth += 3; // keep this in sync with the font's SpaceWidth property - currentWord = ""; - currentText += text[i]; + currentWordStart = index; } else { - currentWidth += - MyGUI::FontManager::getInstance().getByName (mTextStyle.mFont == "Default" ? "EB Garamond" : mTextStyle.mFont) - ->getGlyphInfo(static_cast(text[i]))->width; - currentWord += text[i]; - currentText += text[i]; + currentWidth += widthForCharGlyph(ch); } if (currentWidth > width) { - currentHeight += 18; // keep this in sync with the font size + currentHeight += currentFontHeight(); currentWidth = 0; - // add size of the current word - unsigned int j=0; - while (jgetGlyphInfo(static_cast(currentWord[j]))->width; - ++j; - } + UTFString word = text.substr(currentWordStart, index - currentWordStart); + for (UTFString::const_iterator it = word.begin(), end = word.end(); it != end; ++it) + currentWidth += widthForCharGlyph(it.getCharacter()); } - - ++i; - } - if (currentHeight > height-spacing) - { - // remove the last word - currentText.erase(currentText.size()-currentWord.size(), currentText.size()); + index += UTFString::_utf16_char_length(ch); } + const size_t pageEnd = (currentHeight > height - spacing && currentWordStart != 0) + ? currentWordStart : index; - result.push_back(currentText); - text.erase(0, currentText.size()); + result.push_back(text.substr(0, pageEnd).asUTF8()); + text.erase(0, pageEnd); } return result; } +float BookTextParser::widthForCharGlyph(unsigned unicodeChar) const +{ + std::string fontName(mTextStyle.mFont == "Default" ? "EB Garamond" : mTextStyle.mFont); + return MyGUI::FontManager::getInstance().getByName(fontName) + ->getGlyphInfo(unicodeChar)->width; +} + +float BookTextParser::currentFontHeight() const +{ + std::string fontName(mTextStyle.mFont == "Default" ? "EB Garamond" : mTextStyle.mFont); + return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight(); +} + MyGUI::IntSize BookTextParser::parse(std::string text, MyGUI::Widget* parent, const int width) { + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + text = Interpreter::fixDefinesBook(text, interpreterContext); + mParent = parent; mWidth = width; mHeight = 0; @@ -177,12 +208,13 @@ MyGUI::IntSize BookTextParser::parse(std::string text, MyGUI::Widget* parent, co MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0)); } + boost::algorithm::replace_all(text, "\n", ""); boost::algorithm::replace_all(text, "
", "\n"); boost::algorithm::replace_all(text, "

", "\n\n"); // remove leading newlines - //while (text[0] == '\n') - // text.erase(0); +// while (text[0] == '\n') +// text.erase(0); // remove trailing " if (text[text.size()-1] == '\"') @@ -267,28 +299,30 @@ void BookTextParser::parseSubText(std::string text) { if (text[0] == '<') { - if (text.find('>') == std::string::npos) + const size_t tagStart = 1; + const size_t tagEnd = text.find('>', tagStart); + if (tagEnd == std::string::npos) throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + const std::string tag = text.substr(tagStart, tagEnd - tagStart); - if (text.size() > 4 && text.substr(0, 4) == "'))); - else if (text.size() > 5 && text.substr(0, 5) == "'))); - else if (text.size() > 4 && text.substr(0, 4) == "'))); + if (boost::algorithm::starts_with(tag, "IMG")) + parseImage(tag); + if (boost::algorithm::starts_with(tag, "FONT")) + parseFont(tag); + if (boost::algorithm::starts_with(tag, "DOV")) + parseDiv(tag); - text.erase(0, text.find('>')+1); + text.erase(0, tagEnd + 1); } - bool tagFound = false; + size_t tagStart = std::string::npos; std::string realText; // real text, without tags - unsigned int i=0; - for (; isetSize(box->getSize().width, box->getTextSize().height); mHeight += box->getTextSize().height; - if (tagFound) + if (tagStart != std::string::npos) { - parseSubText(text.substr(i, text.size())); + parseSubText(text.substr(tagStart, text.size())); } } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index a1e115491..ab1ee3af4 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -40,6 +40,8 @@ namespace MWGui std::vector split(std::string text, const int width, const int height); protected: + float widthForCharGlyph(unsigned unicodeChar) const; + float currentFontHeight() const; void parseSubText(std::string text); void parseImage(std::string tag, bool createWidget=true); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 9b4075f57..0a31a428b 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -2,7 +2,8 @@ #include -#include +#include +#include #include @@ -19,6 +20,7 @@ #include "inventorywindow.hpp" #include "container.hpp" #include "console.hpp" +#include "spellicons.hpp" using namespace MWGui; @@ -32,7 +34,6 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) , mWeapStatus(NULL) , mSpellStatus(NULL) , mEffectBox(NULL) - , mEffect1(NULL) , mMinimap(NULL) , mCompass(NULL) , mCrosshair(NULL) @@ -86,9 +87,7 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) mSpellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); getWidget(mEffectBox, "EffectBox"); - getWidget(mEffect1, "Effect1"); mEffectBoxBaseRight = viewSize.width - mEffectBox->getRight(); - mEffectBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); getWidget(mMinimapBox, "MiniMapBox"); mMinimapBoxBaseRight = viewSize.width - mMinimapBox->getRight(); @@ -107,13 +106,18 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) getWidget(mTriangleCounter, "TriangleCounter"); getWidget(mBatchCounter, "BatchCounter"); - setEffect("icons\\s\\tx_s_chameleon.dds"); - LocalMapBase::init(mMinimap, mCompass, this); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); mMainWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &HUD::onWorldMouseLostFocus); + + mSpellIcons = new SpellIcons(); +} + +HUD::~HUD() +{ + delete mSpellIcons; } void HUD::setFpsLevel(int level) @@ -156,11 +160,6 @@ void HUD::setBatchCount(unsigned int count) mBatchCounter->setCaption(boost::lexical_cast(count)); } -void HUD::setEffect(const char *img) -{ - mEffect1->setImageTexture(img); -} - void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { static const char *ids[] = @@ -220,9 +219,9 @@ void HUD::onWorldClicked(MyGUI::Widget* _sender) if (world->canPlaceObject(mouseX, mouseY)) world->placeObject(object, mouseX, mouseY); else - world->dropObjectOnGround(object); + world->dropObjectOnGround(world->getPlayer().getPlayer(), object); - MyGUI::PointerManager::getInstance().setPointer("arrow"); + MWBase::Environment::get().getWindowManager()->changePointer("arrow"); std::string sound = MWWorld::Class::get(object).getDownSoundId(object); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); @@ -244,8 +243,7 @@ void HUD::onWorldClicked(MyGUI::Widget* _sender) if ( (mode != GM_Console) && (mode != GM_Container) && (mode != GM_Inventory) ) return; - std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); - MWWorld::Ptr object = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + MWWorld::Ptr object = MWBase::Environment::get().getWorld()->getFacedObject(); if (mode == GM_Console) MWBase::Environment::get().getWindowManager()->getConsole()->setSelectedObject(object); @@ -274,21 +272,21 @@ void HUD::onWorldMouseOver(MyGUI::Widget* _sender, int x, int y) bool canDrop = world->canPlaceObject(mouseX, mouseY); if (!canDrop) - MyGUI::PointerManager::getInstance().setPointer("drop_ground"); + MWBase::Environment::get().getWindowManager()->changePointer("drop_ground"); else - MyGUI::PointerManager::getInstance().setPointer("arrow"); + MWBase::Environment::get().getWindowManager()->changePointer("arrow"); } else { - MyGUI::PointerManager::getInstance().setPointer("arrow"); + MWBase::Environment::get().getWindowManager()->changePointer("arrow"); mWorldMouseOver = true; } } void HUD::onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new) { - MyGUI::PointerManager::getInstance().setPointer("arrow"); + MWBase::Environment::get().getWindowManager()->changePointer("arrow"); mWorldMouseOver = false; } @@ -319,7 +317,7 @@ void HUD::setCellName(const std::string& cellName) mCellNameTimer = 5.0f; mCellName = cellName; - mCellNameBox->setCaption(mCellName); + mCellNameBox->setCaptionWithReplacing("#{sCell=" + mCellName + "}"); mCellNameBox->setVisible(mMapVisible); } } @@ -543,3 +541,8 @@ void HUD::updatePositions() mMapVisible = mMinimapBox->getVisible (); mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } + +void HUD::update() +{ + mSpellIcons->updateWidgets(mEffectBox, true); +} diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 013ad59f0..aab9e62a4 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -8,12 +8,13 @@ namespace MWGui { class DragAndDrop; + class SpellIcons; class HUD : public OEngine::GUI::Layout, public LocalMapBase { public: HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop); - void setEffect(const char *img); + virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setFPS(float fps); void setTriangleCount(unsigned int count); @@ -43,6 +44,10 @@ namespace MWGui bool getWorldMouseOver() { return mWorldMouseOver; } + MyGUI::Widget* getEffectBox() { return mEffectBox; } + + void update(); + private: MyGUI::ProgressPtr mHealth, mMagicka, mStamina; MyGUI::Widget* mHealthFrame; @@ -51,7 +56,6 @@ namespace MWGui MyGUI::ProgressPtr mWeapStatus, mSpellStatus; MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; - MyGUI::ImageBox* mEffect1; MyGUI::ScrollView* mMinimap; MyGUI::ImageBox* mCompass; MyGUI::ImageBox* mCrosshair; @@ -60,7 +64,7 @@ namespace MWGui MyGUI::Widget* mDummy; - MyGUI::WidgetPtr mFpsBox; + MyGUI::Widget* mFpsBox; MyGUI::TextBox* mFpsCounter; MyGUI::TextBox* mTriangleCounter; MyGUI::TextBox* mBatchCounter; @@ -85,6 +89,8 @@ namespace MWGui bool mWorldMouseOver; + SpellIcons* mSpellIcons; + void onWorldClicked(MyGUI::Widget* _sender); void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new); diff --git a/apps/openmw/mwgui/imagebutton.cpp b/apps/openmw/mwgui/imagebutton.cpp new file mode 100644 index 000000000..98f05373b --- /dev/null +++ b/apps/openmw/mwgui/imagebutton.cpp @@ -0,0 +1,63 @@ +#include "imagebutton.hpp" + +#include + +namespace MWGui +{ + + void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value) + { + if (_key == "ImageHighlighted") + mImageHighlighted = _value; + else if (_key == "ImagePushed") + mImagePushed = _value; + else if (_key == "ImageNormal") + { + if (mImageNormal == "") + { + setImageTexture(_value); + } + mImageNormal = _value; + } + else + ImageBox::setPropertyOverride(_key, _value); + } + void ImageButton::onMouseSetFocus(Widget* _old) + { + setImageTexture(mImageHighlighted); + ImageBox::onMouseSetFocus(_old); + } + + void ImageButton::onMouseLostFocus(Widget* _new) + { + setImageTexture(mImageNormal); + ImageBox::onMouseLostFocus(_new); + } + + void ImageButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) + { + if (_id == MyGUI::MouseButton::Left) + setImageTexture(mImagePushed); + + ImageBox::onMouseButtonPressed(_left, _top, _id); + } + + MyGUI::IntSize ImageButton::getRequestedSize() + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal); + if (texture.isNull()) + { + std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + return MyGUI::IntSize(0,0); + } + return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); + } + + void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) + { + if (_id == MyGUI::MouseButton::Left) + setImageTexture(mImageHighlighted); + + ImageBox::onMouseButtonReleased(_left, _top, _id); + } +} diff --git a/apps/openmw/mwgui/imagebutton.hpp b/apps/openmw/mwgui/imagebutton.hpp new file mode 100644 index 000000000..f531e2246 --- /dev/null +++ b/apps/openmw/mwgui/imagebutton.hpp @@ -0,0 +1,33 @@ +#ifndef MWGUI_IMAGEBUTTON_H +#define MWGUI_IMAGEBUTTON_H + +#include + +namespace MWGui +{ + + /** + * @brief allows using different image textures depending on the button state + */ + class ImageButton : public MyGUI::ImageBox + { + MYGUI_RTTI_DERIVED(ImageButton) + + public: + MyGUI::IntSize getRequestedSize(); + + protected: + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + virtual void onMouseLostFocus(MyGUI::Widget* _new); + virtual void onMouseSetFocus(MyGUI::Widget* _old); + virtual void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id); + virtual void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id); + + std::string mImageHighlighted; + std::string mImageNormal; + std::string mImagePushed; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index bb3dc67e6..ab7615c0e 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -24,19 +26,6 @@ #include "scrollwindow.hpp" #include "spellwindow.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } -} - namespace MWGui { @@ -80,7 +69,7 @@ namespace MWGui setCoord(0, 342, 498, 258); - MWBase::Environment::get().getWorld ()->setupExternalRendering (mPreview); + mPreview.setup(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); openContainer(player); @@ -171,11 +160,8 @@ namespace MWGui // the "Take" button should not be visible. // NOTE: the take button is "reset" when the window opens, so we can safely do the following // without screwing up future book windows - if (mDragAndDrop->mDraggedFrom == this) - { - mWindowManager.getBookWindow()->setTakeButtonShow(false); - mWindowManager.getScrollWindow()->setTakeButtonShow(false); - } + mWindowManager.getBookWindow()->setTakeButtonShow(false); + mWindowManager.getScrollWindow()->setTakeButtonShow(false); mDragAndDrop->mIsOnDragAndDrop = false; MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); @@ -225,24 +211,6 @@ namespace MWGui return MWWorld::Ptr(); } - std::vector InventoryWindow::getEquippedItems() - { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - - std::vector items; - - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); - if (it != invStore.end()) - { - items.push_back(*it); - } - } - - return items; - } - void InventoryWindow::_unequipItem(MWWorld::Ptr item) { MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); @@ -253,6 +221,12 @@ namespace MWGui if (it != invStore.end() && *it == item) { invStore.equip(slot, invStore.end()); + std::string script = MWWorld::Class::get(*it).getScript(*it); + + // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared + if(script != "") + (*it).mRefData->getLocals().setVarByInt(script, "onpcequip", 0); + return; } } @@ -284,7 +258,7 @@ namespace MWGui for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { - if (toLower(it->getCellRef().mRefID) == "gold_001") + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) return it->getRefData().getCount(); } return 0; diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 6b45a9980..7c59bab50 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -64,7 +64,6 @@ namespace MWGui virtual bool isTrading() { return mTrading; } virtual bool isInventory() { return true; } - virtual std::vector getEquippedItems(); virtual void _unequipItem(MWWorld::Ptr item); virtual void onReferenceUnavailable() { ; } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 9d5d236be..ba39b0101 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -83,10 +83,9 @@ book formatText(std::string text,book mBook,int maxLine, int lineSize) MWGui::JournalWindow::JournalWindow (MWBase::WindowManager& parWindowManager) : WindowBase("openmw_journal.layout", parWindowManager) - , mLastPos(0) - , mVisible(false) , mPageNumber(0) { + mMainWidget->setVisible(false); //setCoord(0,0,498, 342); center(); @@ -115,20 +114,15 @@ MWGui::JournalWindow::JournalWindow (MWBase::WindowManager& parWindowManager) //displayLeftText(list.front()); } -void MWGui::JournalWindow::setVisible(bool visible) +void MWGui::JournalWindow::close() { - if (mVisible && !visible) - MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); - mVisible = visible; - - mMainWidget->setVisible(visible); + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); } void MWGui::JournalWindow::open() { mPageNumber = 0; - std::string journalOpenSound = "book open"; - MWBase::Environment::get().getSoundManager()->playSound (journalOpenSound, 1.0, 1.0); + MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); if(MWBase::Environment::get().getJournal()->begin()!=MWBase::Environment::get().getJournal()->end()) { book journal; @@ -182,7 +176,7 @@ void MWGui::JournalWindow::displayRightText(std::string text) } -void MWGui::JournalWindow::notifyNextPage(MyGUI::WidgetPtr _sender) +void MWGui::JournalWindow::notifyNextPage(MyGUI::Widget* _sender) { if(mPageNumber < int(mLeftPages.size())-1) { @@ -194,7 +188,7 @@ void MWGui::JournalWindow::notifyNextPage(MyGUI::WidgetPtr _sender) } } -void MWGui::JournalWindow::notifyPrevPage(MyGUI::WidgetPtr _sender) +void MWGui::JournalWindow::notifyPrevPage(MyGUI::Widget* _sender) { if(mPageNumber > 0) { diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index a62e48803..cd1ff7ebb 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -7,6 +7,7 @@ #include #include "window_base.hpp" +#include "imagebutton.hpp" namespace MWGui { @@ -15,8 +16,7 @@ namespace MWGui public: JournalWindow(MWBase::WindowManager& parWindowManager); virtual void open(); - - virtual void setVisible(bool visible); // only used to play close sound + virtual void close(); private: void displayLeftText(std::string text); @@ -26,22 +26,16 @@ namespace MWGui /** *Called when next/prev button is used. */ - void notifyNextPage(MyGUI::WidgetPtr _sender); - void notifyPrevPage(MyGUI::WidgetPtr _sender); + void notifyNextPage(MyGUI::Widget* _sender); + void notifyPrevPage(MyGUI::Widget* _sender); - static const int sLineHeight; - - MyGUI::WidgetPtr mSkillAreaWidget, mSkillClientWidget; - MyGUI::ScrollBar* mSkillScrollerWidget; - int mLastPos, mClientHeight; - MyGUI::EditPtr mLeftTextWidget; - MyGUI::EditPtr mRightTextWidget; - MyGUI::ButtonPtr mPrevBtn; - MyGUI::ButtonPtr mNextBtn; + MyGUI::EditBox* mLeftTextWidget; + MyGUI::EditBox* mRightTextWidget; + MWGui::ImageButton* mPrevBtn; + MWGui::ImageButton* mNextBtn; std::vector mLeftPages; std::vector mRightPages; int mPageNumber; //store the number of the current left page - bool mVisible; }; } diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index 0bafced97..d60e9b687 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -1,6 +1,9 @@ #include "list.hpp" -#include +#include +#include +#include +#include using namespace MWGui; using namespace MWGui::Widgets; diff --git a/apps/openmw/mwgui/list.hpp b/apps/openmw/mwgui/list.hpp index d07d49de6..38797e779 100644 --- a/apps/openmw/mwgui/list.hpp +++ b/apps/openmw/mwgui/list.hpp @@ -1,7 +1,12 @@ #ifndef MWGUI_LIST_HPP #define MWGUI_LIST_HPP -#include +#include + +namespace MyGUI +{ + class ScrollView; +} namespace MWGui { diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index d721e209a..e7c7acb53 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -6,6 +6,7 @@ #include #include + #include #include @@ -16,6 +17,9 @@ #include "../mwbase/windowmanager.hpp" +#include + + namespace MWGui { @@ -102,7 +106,7 @@ namespace MWGui float progress = (float(mCurrentCellLoading)+refProgress) / float(mTotalCellsLoading); assert(progress <= 1 && progress >= 0); - mLoadingText->setCaption(stage + "... "); + mLoadingText->setCaption(stage); mProgressBar->setProgressPosition (static_cast(progress * 1000)); static float loadingScreenFps = 30.f; @@ -213,24 +217,16 @@ namespace MWGui void LoadingScreen::changeWallpaper () { - std::vector splash; + if (mResources.isNull ()) + mResources = Ogre::ResourceGroupManager::getSingleton ().findResourceNames ("General", "Splash_*.tga"); - Ogre::StringVectorPtr resources = Ogre::ResourceGroupManager::getSingleton ().listResourceNames ("General", false); - for (Ogre::StringVector::const_iterator it = resources->begin(); it != resources->end(); ++it) - { - if (it->size() < 6) - continue; - std::string start = it->substr(0, 6); - boost::to_lower(start); - if (start == "splash") - splash.push_back (*it); - } - if (splash.size()) + if (mResources->size()) { - std::string randomSplash = splash[rand() % splash.size()]; + std::string const & randomSplash = mResources->at (rand() % mResources->size()); Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, "General"); + mBackgroundImage->setImageTexture (randomSplash); } else diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index a012793ca..24b385071 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -19,6 +19,8 @@ namespace MWGui void onResChange(int w, int h); + void updateWindow(Ogre::RenderWindow* rw) { mWindow = rw; } + private: bool mFirstLoad; @@ -42,6 +44,7 @@ namespace MWGui Ogre::Rectangle2D* mRectangle; Ogre::MaterialPtr mBackgroundMaterial; + Ogre::StringVectorPtr mResources; bool mLoadingOn; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index e98b75e9b..5402d3542 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -5,6 +5,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" namespace MWGui { @@ -20,65 +21,58 @@ namespace MWGui { setCoord(0,0,w,h); - int height = 64 * 3; if (mButtonBox) MyGUI::Gui::getInstance ().destroyWidget(mButtonBox); - mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(w/2 - 64, h/2 - height/2, 128, height), MyGUI::Align::Default); + mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); int curH = 0; - mReturn = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mReturn->setImageResource ("Menu_Return"); - mReturn->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::returnToGame); - curH += 64; + std::vector buttons; + buttons.push_back("return"); + //buttons.push_back("newgame"); + //buttons.push_back("loadgame"); + //buttons.push_back("savegame"); + buttons.push_back("options"); + //buttons.push_back("credits"); + buttons.push_back("exitgame"); + int maxwidth = 0; - /* - mNewGame = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mNewGame->setImageResource ("Menu_NewGame"); - curH += 64; + mButtons.clear(); + for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) + { + MWGui::ImageButton* button = mButtonBox->createWidget + ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); + button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds"); + button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds"); + button->setProperty("ImagePushed", "textures\\menu_" + *it + "_pressed.dds"); + MyGUI::IntSize requested = button->getRequestedSize(); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); + mButtons[*it] = button; + curH += requested.height; - mLoadGame = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mLoadGame->setImageResource ("Menu_LoadGame"); - curH += 64; + if (requested.width > maxwidth) + maxwidth = requested.width; + } + for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) + { + MyGUI::IntSize requested = it->second->getRequestedSize(); + it->second->setCoord((maxwidth-requested.width) / 2, it->second->getTop(), requested.width, requested.height); + } - - mSaveGame = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mSaveGame->setImageResource ("Menu_SaveGame"); - curH += 64; - */ - - mOptions = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mOptions->setImageResource ("Menu_Options"); - mOptions->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::showOptions); - curH += 64; - - /* - mCredits = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mCredits->setImageResource ("Menu_Credits"); - curH += 64; - */ - - mExitGame = mButtonBox->createWidget ("ButtonImage", MyGUI::IntCoord(0, curH, 128, 64), MyGUI::Align::Default); - mExitGame->setImageResource ("Menu_ExitGame"); - mExitGame->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::exitGame); - curH += 64; + mButtonBox->setCoord (w/2 - maxwidth/2, h/2 - curH/2, maxwidth, curH); } - void MainMenu::returnToGame(MyGUI::Widget* sender) + void MainMenu::onButtonClicked(MyGUI::Widget *sender) { - MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); - } - - void MainMenu::showOptions(MyGUI::Widget* sender) - { - MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); - } - - void MainMenu::exitGame(MyGUI::Widget* sender) - { - Ogre::Root::getSingleton ().queueEndRendering (); + MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); + if (sender == mButtons["return"]) + MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); + else if (sender == mButtons["options"]) + MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); + else if (sender == mButtons["exitgame"]) + Ogre::Root::getSingleton ().queueEndRendering (); } } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index fd583d187..4e76a64df 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -1,5 +1,7 @@ #include +#include "imagebutton.hpp" + namespace MWGui { @@ -11,19 +13,11 @@ namespace MWGui void onResChange(int w, int h); private: - MyGUI::Button* mReturn; - MyGUI::Button* mNewGame; - MyGUI::Button* mLoadGame; - MyGUI::Button* mSaveGame; - MyGUI::Button* mOptions; - MyGUI::Button* mCredits; - MyGUI::Button* mExitGame; - MyGUI::Widget* mButtonBox; - void returnToGame(MyGUI::Widget* sender); - void showOptions(MyGUI::Widget* sender); - void exitGame(MyGUI::Widget* sender); + std::map mButtons; + + void onButtonClicked (MyGUI::Widget* sender); }; } diff --git a/apps/openmw/mwgui/map_window.cpp b/apps/openmw/mwgui/map_window.cpp index 0a26ebb8f..52b108f85 100644 --- a/apps/openmw/mwgui/map_window.cpp +++ b/apps/openmw/mwgui/map_window.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -13,6 +15,8 @@ #include "../mwrender/globalmap.hpp" +#include "widgets.hpp" + using namespace MWGui; LocalMapBase::LocalMapBase() @@ -88,7 +92,7 @@ void LocalMapBase::applyFogOfWar() + boost::lexical_cast(my); std::string image = mPrefix+"_"+ boost::lexical_cast(mCurX + (mx-1)) + "_" - + boost::lexical_cast(mCurY + (mInterior ? (my-1) : -1*(my-1))); + + boost::lexical_cast(mCurY + (-1*(my-1))); MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx]; fog->setImageTexture(mFogOfWar ? ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" @@ -96,6 +100,7 @@ void LocalMapBase::applyFogOfWar() : ""); } } + notifyMapChanged (); } void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2) @@ -127,7 +132,7 @@ void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { // map std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); + + boost::lexical_cast(y + (-1*(my-1))); std::string name = "Map_" + boost::lexical_cast(mx) + "_" + boost::lexical_cast(my); @@ -173,7 +178,7 @@ void LocalMapBase::setActiveCell(const int x, const int y, bool interior) } else { - Ogre::Vector2 position (marker.x, -marker.y); + Ogre::Vector2 position (marker.x, marker.y); MWBase::Environment::get().getWorld ()->getInteriorMapPosition (position, nX, nY, cellDx, cellDy); widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + (1+cellDx-x) * 512, nY * 512 - 4 + (1+cellDy-y) * 512, 8, 8); @@ -299,7 +304,7 @@ MapWindow::~MapWindow() void MapWindow::setCellName(const std::string& cellName) { - setTitle(cellName); + setTitle("#{sCell=" + cellName + "}"); } void MapWindow::addVisitedLocation(const std::string& name, int x, int y) @@ -394,10 +399,10 @@ void MapWindow::globalMapUpdatePlayer () { Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition (); Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation (); - Ogre::Vector2 dir (orient.yAxis ().x, -orient.yAxis().z); + Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y); float worldX, worldY; - mGlobalMapRender->worldPosToImageSpace (pos.x, pos.z, worldX, worldY); + mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); worldX *= mGlobalMapRender->getWidth(); worldY *= mGlobalMapRender->getHeight(); @@ -425,3 +430,17 @@ void MapWindow::notifyPlayerUpdate () { globalMapUpdatePlayer (); } + +void MapWindow::notifyMapChanged () +{ + // workaround to prevent the map from drawing on top of the button + MyGUI::IntCoord oldCoord = mButton->getCoord (); + MyGUI::Gui::getInstance().destroyWidget (mButton); + mButton = mMainWidget->createWidget("MW_Button", + oldCoord, MyGUI::Align::Bottom | MyGUI::Align::Right); + mButton->setProperty ("ExpandDirection", "Left"); + + mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); + mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : + "#{sWorld}"); +} diff --git a/apps/openmw/mwgui/map_window.hpp b/apps/openmw/mwgui/map_window.hpp index 4e2dd6756..39770a7a2 100644 --- a/apps/openmw/mwgui/map_window.hpp +++ b/apps/openmw/mwgui/map_window.hpp @@ -50,6 +50,7 @@ namespace MWGui void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2); virtual void notifyPlayerUpdate() {} + virtual void notifyMapChanged() {} OEngine::GUI::Layout* mLayout; @@ -99,6 +100,8 @@ namespace MWGui virtual void onPinToggled(); virtual void notifyPlayerUpdate(); + virtual void notifyMapChanged(); + }; } #endif diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index b660af7dd..b8a34c457 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -1,4 +1,8 @@ +#include + #include "messagebox.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" using namespace MWGui; @@ -133,6 +137,10 @@ void MessageBoxManager::setMessageBoxSpeed (int speed) mMessageBoxSpeed = speed; } +void MessageBoxManager::enterPressed () +{ + mInterMessageBoxe->enterPressed(); +} int MessageBoxManager::readPressedButton () { @@ -239,7 +247,7 @@ InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxMan std::vector::const_iterator it; for(it = buttons.begin(); it != buttons.end(); ++it) { - MyGUI::ButtonPtr button = mButtonsWidget->createWidget( + MyGUI::Button* button = mButtonsWidget->createWidget( MyGUI::WidgetStyle::Child, std::string("MW_Button"), dummyCoord, @@ -293,7 +301,7 @@ InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxMan MyGUI::IntSize buttonSize(0, buttonHeight); int left = (mainWidgetSize.width - buttonsWidth)/2 + buttonPadding; - std::vector::const_iterator button; + std::vector::const_iterator button; for(button = mButtons.begin(); button != mButtons.end(); ++button) { buttonCord.left = left; @@ -341,7 +349,7 @@ InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxMan int top = textButtonPadding + buttonTopPadding + textSize.height; - std::vector::const_iterator button; + std::vector::const_iterator button; for(button = mButtons.begin(); button != mButtons.end(); ++button) { buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; @@ -359,11 +367,33 @@ InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxMan } } +void InteractiveMessageBox::enterPressed() +{ + + std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}")); + std::vector::const_iterator button; + for(button = mButtons.begin(); button != mButtons.end(); ++button) + { + if(Misc::StringUtils::lowerCase((*button)->getCaption()) == ok) + { + buttonActivated(*button); + MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); + break; + } + } + +} + void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) +{ + buttonActivated (pressed); +} + +void InteractiveMessageBox::buttonActivated (MyGUI::Widget* pressed) { mMarkedToDelete = true; int index = 0; - std::vector::const_iterator button; + std::vector::const_iterator button; for(button = mButtons.begin(); button != mButtons.end(); ++button) { if(*button == pressed) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 5e4c468d5..149aa7e7f 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -2,7 +2,6 @@ #define MWGUI_MESSAGE_BOX_H #include -#include #include "window_base.hpp" @@ -10,6 +9,13 @@ #undef MessageBox +namespace MyGUI +{ + class Widget; + class Button; + class EditBox; +} + namespace MWGui { class InteractiveMessageBox; @@ -34,7 +40,8 @@ namespace MWGui void removeMessageBox (float time, MessageBox *msgbox); bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); - + + void enterPressed(); int readPressedButton (); MWBase::WindowManager *mWindowManager; @@ -60,7 +67,7 @@ namespace MWGui MessageBoxManager& mMessageBoxManager; int mHeight; const std::string& mMessage; - MyGUI::EditPtr mMessageWidget; + MyGUI::EditBox* mMessageWidget; int mFixedWidth; int mBottomPadding; int mNextBoxPadding; @@ -70,16 +77,19 @@ namespace MWGui { public: InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons); + void enterPressed (); void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); bool mMarkedToDelete; private: + void buttonActivated (MyGUI::Widget* _widget); + MessageBoxManager& mMessageBoxManager; - MyGUI::EditPtr mMessageWidget; - MyGUI::WidgetPtr mButtonsWidget; - std::vector mButtons; + MyGUI::EditBox* mMessageWidget; + MyGUI::Widget* mButtonsWidget; + std::vector mButtons; int mTextButtonPadding; int mButtonPressed; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index d791a1c99..319537297 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -45,7 +45,9 @@ namespace MWGui GM_Loading, GM_LoadingWallpaper, - GM_QuickKeysMenu + GM_QuickKeysMenu, + + GM_Video }; // Windows shown in inventory mode diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 02512425d..6d51420f0 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -440,7 +440,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - spellList.push_back(*it); + spellList.push_back (it->first); } const MWWorld::ESMStore &esmStore = diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index d2714fb51..1436995c5 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -20,7 +20,7 @@ using namespace MWGui; using namespace Widgets; RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_race.layout", parWindowManager) + : WindowModal("openmw_chargen_race.layout", parWindowManager) , mGenderIndex(0) , mFaceIndex(0) , mHairIndex(0) @@ -41,7 +41,7 @@ RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons - MyGUI::ButtonPtr prevButton, nextButton; + MyGUI::Button *prevButton, *nextButton; setText("GenderChoiceT", mWindowManager.getGameSettingString("sRaceMenu2", "Change Sex")); getWidget(prevButton, "PrevGenderButton"); @@ -61,7 +61,7 @@ RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextHair); - setText("RaceT", mWindowManager.getGameSettingString("sRaceMenu4", "Race")); + setText("RaceT", mWindowManager.getGameSettingString("sRaceMenu5", "Race")); getWidget(mRaceList, "RaceList"); mRaceList->setScrollVisible(true); mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); @@ -73,15 +73,14 @@ RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) setText("SpellPowerT", mWindowManager.getGameSettingString("sRaceMenu7", "Specials")); getWidget(mSpellPowerList, "SpellPowerList"); - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); - okButton->setEnabled(false); updateRaces(); updateSkills(); @@ -90,7 +89,7 @@ RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) void RaceDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) @@ -101,12 +100,14 @@ void RaceDialog::setNextButtonShow(bool shown) void RaceDialog::open() { + WindowModal::open(); + updateRaces(); updateSkills(); updateSpellPowers(); mPreview = new MWRender::RaceSelectionPreview(); - MWBase::Environment::get().getWorld ()->setupExternalRendering (*mPreview); + mPreview->setup(); mPreview->update (0); const ESM::NPC proto = mPreview->getPrototype(); @@ -133,9 +134,8 @@ void RaceDialog::setRaceId(const std::string &raceId) if (boost::iequals(*mRaceList->getItemDataAt(i), raceId)) { mRaceList->setIndexSelected(i); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); break; } } @@ -256,9 +256,8 @@ void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setEnabled(true); const std::string *raceId = mRaceList->getItemDataAt(_index); if (boost::iequals(mCurrentRaceId, *raceId)) return; @@ -332,7 +331,7 @@ void RaceDialog::updateRaces() void RaceDialog::updateSkills() { - for (std::vector::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it) + for (std::vector::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } @@ -370,7 +369,7 @@ void RaceDialog::updateSkills() void RaceDialog::updateSpellPowers() { - for (std::vector::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it) + for (std::vector::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index e0dc3306a..efd08f439 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -23,7 +23,7 @@ namespace MWGui namespace MWGui { - class RaceDialog : public WindowBase + class RaceDialog : public WindowModal { public: RaceDialog(MWBase::WindowManager& parWindowManager); @@ -85,11 +85,11 @@ namespace MWGui MyGUI::ListBox* mRaceList; MyGUI::ScrollBar* mHeadRotate; - MyGUI::WidgetPtr mSkillList; - std::vector mSkillItems; + MyGUI::Widget* mSkillList; + std::vector mSkillItems; - MyGUI::WidgetPtr mSpellPowerList; - std::vector mSpellPowerItems; + MyGUI::Widget* mSpellPowerList; + std::vector mSpellPowerItems; int mGenderIndex, mFaceIndex, mHairIndex; int mFaceCount, mHairCount; diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 45adb5383..50508cc5f 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -23,7 +23,7 @@ using namespace Widgets; const int ReviewDialog::sLineHeight = 18; ReviewDialog::ReviewDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_chargen_review.layout", parWindowManager) + : WindowModal("openmw_chargen_review.layout", parWindowManager) , mLastPos(0) { // Centre dialog @@ -86,17 +86,18 @@ ReviewDialog::ReviewDialog(MWBase::WindowManager& parWindowManager) mSkillWidgetMap.insert(std::make_pair(i, static_cast (0))); } - MyGUI::ButtonPtr backButton; + MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked); } void ReviewDialog::open() { + WindowModal::open(); updateSkillArea(); } @@ -308,7 +309,7 @@ void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId void ReviewDialog::updateSkillArea() { - for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) + for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 2b0740234..4f41ec42d 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -17,7 +17,7 @@ Layout is defined by resources/mygui/openmw_chargen_review.layout. namespace MWGui { - class ReviewDialog : public WindowBase + class ReviewDialog : public WindowModal { public: enum Dialogs { @@ -91,7 +91,7 @@ namespace MWGui std::map mSkillWidgetMap; std::string mName, mRaceId, mBirthSignId; ESM::Class mKlass; - std::vector mSkillWidgets; //< Skills and other information + std::vector mSkillWidgets; //< Skills and other information }; } #endif diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 8317025e0..3bd3a4743 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -12,8 +12,10 @@ using namespace MWGui; -ScrollWindow::ScrollWindow (MWBase::WindowManager& parWindowManager) : - WindowBase("openmw_scroll.layout", parWindowManager) +ScrollWindow::ScrollWindow (MWBase::WindowManager& parWindowManager) + : WindowBase("openmw_scroll.layout", parWindowManager) + , mTakeButtonShow(true) + , mTakeButtonAllowed(true) { getWidget(mTextView, "TextView"); @@ -50,7 +52,14 @@ void ScrollWindow::open (MWWorld::Ptr scroll) void ScrollWindow::setTakeButtonShow(bool show) { - mTakeButton->setVisible(show); + mTakeButtonShow = show; + mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); +} + +void ScrollWindow::setInventoryAllowed(bool allowed) +{ + mTakeButtonAllowed = allowed; + mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index b8f52fb65..42b6395a9 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -2,6 +2,7 @@ #define MWGUI_SCROLLWINDOW_H #include "window_base.hpp" +#include "imagebutton.hpp" #include "../mwworld/ptr.hpp" @@ -14,17 +15,22 @@ namespace MWGui void open (MWWorld::Ptr scroll); void setTakeButtonShow(bool show); + void setInventoryAllowed(bool allowed); protected: void onCloseButtonClicked (MyGUI::Widget* _sender); void onTakeButtonClicked (MyGUI::Widget* _sender); private: - MyGUI::Button* mCloseButton; - MyGUI::Button* mTakeButton; + MWGui::ImageButton* mCloseButton; + MWGui::ImageButton* mTakeButton; MyGUI::ScrollView* mTextView; MWWorld::Ptr mScroll; + + bool mTakeButtonShow; + bool mTakeButtonAllowed; + }; } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index cdfe4d2b6..04856c3ed 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -77,6 +78,17 @@ namespace { return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos) ? "glsl" : "hlsl"; } + + bool cgAvailable () + { + Ogre::Root::PluginInstanceList list = Ogre::Root::getSingleton ().getInstalledPlugins (); + for (Ogre::Root::PluginInstanceList::const_iterator it = list.begin(); it != list.end(); ++it) + { + if ((*it)->getName() == "Cg Program Manager") + return true; + } + return false; + } } namespace MWGui @@ -109,6 +121,7 @@ namespace MWGui getWidget(mReflectActorsButton, "ReflectActorsButton"); getWidget(mReflectTerrainButton, "ReflectTerrainButton"); getWidget(mShadersButton, "ShadersButton"); + getWidget(mShaderModeButton, "ShaderModeButton"); getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); getWidget(mShadowsLargeDistance, "ShadowsLargeDistance"); getWidget(mShadowsTextureSize, "ShadowsTextureSize"); @@ -116,22 +129,22 @@ namespace MWGui getWidget(mStaticsShadows, "StaticsShadows"); getWidget(mMiscShadows, "MiscShadows"); getWidget(mShadowsDebug, "ShadowsDebug"); - getWidget(mUnderwaterButton, "UnderwaterButton"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mInvertYButton, "InvertYButton"); getWidget(mUISensitivitySlider, "UISensitivitySlider"); getWidget(mCameraSensitivitySlider, "CameraSensitivitySlider"); - getWidget(mGammaSlider, "GammaSlider"); + getWidget(mRefractionButton, "RefractionButton"); mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); - mUnderwaterButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); + mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mRefractionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -144,7 +157,6 @@ namespace MWGui mViewDistanceSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); mAnisotropySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mGammaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -202,14 +214,6 @@ namespace MWGui getWidget(fovText, "FovText"); fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getFloat("field of view", "General"))) + ")"); - float gammaVal = (Settings::Manager::getFloat("gamma", "Video")-0.1f)/(3.f-0.1f); - mGammaSlider->setScrollPosition(gammaVal * (mGammaSlider->getScrollRange()-1)); - MyGUI::TextBox* gammaText; - getWidget(gammaText, "GammaText"); - std::stringstream gamma; - gamma << std::setprecision (2) << Settings::Manager::getFloat("gamma", "Video"); - gammaText->setCaption("Gamma (" + gamma.str() + ")"); - float anisotropyVal = Settings::Manager::getInt("anisotropy", "General") / 16.0; mAnisotropySlider->setScrollPosition(anisotropyVal * (mAnisotropySlider->getScrollRange()-1)); std::string tf = Settings::Manager::getString("texture filtering", "General"); @@ -230,10 +234,12 @@ namespace MWGui mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect statics", "Water") ? "#{sOn}" : "#{sOff}"); mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); - mUnderwaterButton->setCaptionWithReplacing(Settings::Manager::getBool("underwater effect", "Water") ? "#{sOn}" : "#{sOff}"); mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}"); + //mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}"); + mShadowsLargeDistance->setCaptionWithReplacing("#{sOff}"); + mShadowsLargeDistance->setEnabled (false); + mShadowsEnabledButton->setCaptionWithReplacing(Settings::Manager::getBool("enabled", "Shadows") ? "#{sOn}" : "#{sOff}"); mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); @@ -250,26 +256,14 @@ namespace MWGui mInvertYButton->setCaptionWithReplacing(Settings::Manager::getBool("invert y axis", "Input") ? "#{sOn}" : "#{sOff}"); - std::string shaders; + mShadersButton->setCaptionWithReplacing (Settings::Manager::getBool("shaders", "Objects") ? "#{sOn}" : "#{sOff}"); + mShaderModeButton->setCaption (Settings::Manager::getString("shader mode", "General")); + + mRefractionButton->setCaptionWithReplacing (Settings::Manager::getBool("refraction", "Water") ? "#{sOn}" : "#{sOff}"); + if (!Settings::Manager::getBool("shaders", "Objects")) - shaders = "off"; - else { - shaders = Settings::Manager::getString("shader mode", "General"); - } - mShadersButton->setCaption (shaders); - - if (!MWRender::RenderingManager::waterShaderSupported()) - { - mWaterShaderButton->setEnabled(false); - mReflectObjectsButton->setEnabled(false); - mReflectActorsButton->setEnabled(false); - mReflectTerrainButton->setEnabled(false); - } - - if (shaders == "off") - { - mUnderwaterButton->setEnabled (false); + mRefractionButton->setEnabled(false); mShadowsEnabledButton->setEnabled(false); } @@ -385,12 +379,12 @@ namespace MWGui } else { + if (_sender == mVSyncButton) + Settings::Manager::setBool("vsync", "Video", newState); if (_sender == mWaterShaderButton) Settings::Manager::setBool("shader", "Water", newState); - else if (_sender == mUnderwaterButton) - { - Settings::Manager::setBool("underwater effect", "Water", newState); - } + else if (_sender == mRefractionButton) + Settings::Manager::setBool("refraction", "Water", newState); else if (_sender == mReflectObjectsButton) { Settings::Manager::setBool("reflect misc", "Water", newState); @@ -424,33 +418,44 @@ namespace MWGui } } - void SettingsWindow::onShadersToggled(MyGUI::Widget* _sender) + void SettingsWindow::onShaderModeToggled(MyGUI::Widget* _sender) { std::string val = static_cast(_sender)->getCaption(); - if (val == "off") + if (val == "cg") { val = hlslGlsl(); } - else if (val == hlslGlsl()) + else if (cgAvailable ()) val = "cg"; - else - val = "off"; static_cast(_sender)->setCaption(val); - if (val == "off") + Settings::Manager::setString("shader mode", "General", val); + + apply(); + } + + void SettingsWindow::onShadersToggled(MyGUI::Widget* _sender) + { + std::string on = mWindowManager.getGameSettingString("sOn", "On"); + std::string off = mWindowManager.getGameSettingString("sOff", "On"); + + std::string val = static_cast(_sender)->getCaption(); + if (val == off) + val = on; + else + val = off; + static_cast(_sender)->setCaptionWithReplacing (val); + + if (val == off) { Settings::Manager::setBool("shaders", "Objects", false); - // water shader not supported with object shaders off - mWaterShaderButton->setCaptionWithReplacing("#{sOff}"); - mUnderwaterButton->setCaptionWithReplacing("#{sOff}"); - mWaterShaderButton->setEnabled(false); - mReflectObjectsButton->setEnabled(false); - mReflectActorsButton->setEnabled(false); - mReflectTerrainButton->setEnabled(false); - mUnderwaterButton->setEnabled(false); - Settings::Manager::setBool("shader", "Water", false); + // refraction needs shaders to display underwater fog + mRefractionButton->setCaptionWithReplacing("#{sOff}"); + mRefractionButton->setEnabled(false); + + Settings::Manager::setBool("refraction", "Water", false); Settings::Manager::setBool("underwater effect", "Water", false); // shadows not supported @@ -461,17 +466,13 @@ namespace MWGui else { Settings::Manager::setBool("shaders", "Objects", true); - Settings::Manager::setString("shader mode", "General", val); // re-enable - if (MWRender::RenderingManager::waterShaderSupported()) - { - mWaterShaderButton->setEnabled(true); - mReflectObjectsButton->setEnabled(true); - mReflectActorsButton->setEnabled(true); - mReflectTerrainButton->setEnabled(true); - } - mUnderwaterButton->setEnabled(true); + mReflectObjectsButton->setEnabled(true); + mReflectActorsButton->setEnabled(true); + mReflectTerrainButton->setEnabled(true); + mRefractionButton->setEnabled(true); + mShadowsEnabledButton->setEnabled(true); } @@ -521,15 +522,6 @@ namespace MWGui fovText->setCaption("Field of View (" + boost::lexical_cast(int((1-val) * sFovMin + val * sFovMax)) + ")"); Settings::Manager::setFloat("field of view", "General", (1-val) * sFovMin + val * sFovMax); } - else if (scroller == mGammaSlider) - { - Settings::Manager::setFloat("gamma", "Video", (1-val) * 0.1f + val * 3.f); - MyGUI::TextBox* gammaText; - getWidget(gammaText, "GammaText"); - std::stringstream gamma; - gamma << std::setprecision (2) << Settings::Manager::getFloat("gamma", "Video"); - gammaText->setCaption("Gamma (" + gamma.str() + ")"); - } else if (scroller == mAnisotropySlider) { mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(int(val*16)) + ")"); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index e878d0abe..fc1ec9e36 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -40,7 +40,6 @@ namespace MWGui MyGUI::Button* mFPSButton; MyGUI::ScrollBar* mViewDistanceSlider; MyGUI::ScrollBar* mFOVSlider; - MyGUI::ScrollBar* mGammaSlider; MyGUI::ScrollBar* mAnisotropySlider; MyGUI::Button* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; @@ -50,7 +49,8 @@ namespace MWGui MyGUI::Button* mReflectActorsButton; MyGUI::Button* mReflectTerrainButton; MyGUI::Button* mShadersButton; - MyGUI::Button* mUnderwaterButton; + MyGUI::Button* mShaderModeButton; + MyGUI::Button* mRefractionButton; MyGUI::Button* mShadowsEnabledButton; MyGUI::Button* mShadowsLargeDistance; @@ -84,6 +84,7 @@ namespace MWGui void onResolutionCancel(); void onShadersToggled(MyGUI::Widget* _sender); + void onShaderModeToggled(MyGUI::Widget* _sender); void onShadowTextureSize(MyGUI::Widget* _sender); void onRebindAction(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index a41f401a5..40fcf2988 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -94,23 +94,20 @@ namespace MWGui mPtr = actor; clearSpells(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - - MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells(); MWMechanics::Spells& merchantSpells = MWWorld::Class::get (actor).getCreatureStats (actor).getSpells(); - + for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) { const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find (*iter); - + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers - - if (std::find (playerSpells.begin(), playerSpells.end(), *iter)!=playerSpells.end()) - continue; // we have that spell already - - addSpell (*iter); + + if (playerHasSpell(iter->first)) + continue; + + addSpell (iter->first); } updateLabels(); @@ -118,6 +115,18 @@ namespace MWGui mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); } + bool SpellBuyingWindow::playerHasSpell(const std::string &id) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells(); + for (MWMechanics::Spells::TIterator it = playerSpells.begin(); it != playerSpells.end(); ++it) + { + if (Misc::StringUtils::ciEqual(id, it->first)) + return true; + } + return false; + } + void SpellBuyingWindow::onSpellButtonClick(MyGUI::Widget* _sender) { int price = *_sender->getUserData(); diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 1d0ac28e0..c4988fff3 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -47,6 +47,8 @@ namespace MWGui void updateLabels(); virtual void onReferenceUnavailable(); + + bool playerHasSpell (const std::string& id); }; } diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 69d69519f..839586452 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -436,7 +436,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(*it); + MWBase::Environment::get().getWorld()->getStore().get().find (it->first); // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp new file mode 100644 index 000000000..16e02ebba --- /dev/null +++ b/apps/openmw/mwgui/spellicons.cpp @@ -0,0 +1,293 @@ +#include "spellicons.hpp" + +#include +#include +#include + +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/creaturestats.hpp" + +#include "tooltips.hpp" + + +namespace MWGui +{ + + void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + + std::map > effects; + + // add permanent item enchantments + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = store.getSlot(slot); + if (it == store.end()) + continue; + std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it); + if (enchantment.empty()) + continue; + const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get().find(enchantment); + if (enchant->mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + const ESM::EffectList& list = enchant->mEffects; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = + MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + + MagicEffectInfo effectInfo; + effectInfo.mSource = MWWorld::Class::get(*it).getName(*it); + effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effectInfo.mKey.mArg = effectIt->mSkill; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effectInfo.mKey.mArg = effectIt->mAttribute; + // just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense + effectInfo.mMagnitude = effectIt->mMagnMin; + effectInfo.mPermanent = true; + effects[effectIt->mEffectID].push_back (effectInfo); + } + } + + // add permanent spells + const MWMechanics::Spells& spells = stats.getSpells(); + for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + // these are the spell types that are permanently in effect + if (!(spell->mData.mType == ESM::Spell::ST_Ability) + && !(spell->mData.mType == ESM::Spell::ST_Disease) + && !(spell->mData.mType == ESM::Spell::ST_Curse) + && !(spell->mData.mType == ESM::Spell::ST_Blight)) + continue; + const ESM::EffectList& list = spell->mEffects; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = + MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + MagicEffectInfo effectInfo; + effectInfo.mSource = getSpellDisplayName (it->first); + effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effectInfo.mKey.mArg = effectIt->mSkill; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effectInfo.mKey.mArg = effectIt->mAttribute; + // just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense + effectInfo.mMagnitude = effectIt->mMagnMin; + effectInfo.mPermanent = true; + + effects[effectIt->mEffectID].push_back (effectInfo); + } + } + + // add lasting effect spells/potions etc + const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); + for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); + it != activeSpells.end(); ++it) + { + const ESM::EffectList& list = getSpellEffectList(it->first); + + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = + MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + + MagicEffectInfo effectInfo; + effectInfo.mSource = getSpellDisplayName (it->first); + effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effectInfo.mKey.mArg = effectIt->mSkill; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effectInfo.mKey.mArg = effectIt->mAttribute; + effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * it->second.second; + effectInfo.mRemainingTime = effectIt->mDuration + + (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + + // ingredients need special casing for their magnitude / duration + /// \todo duplicated from ActiveSpells, helper function? + if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) + { + effectInfo.mRemainingTime = effectIt->mDuration * it->second.second + + (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + + effectInfo.mMagnitude = static_cast (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost)); + } + + + effects[effectIt->mEffectID].push_back (effectInfo); + } + } + + parent->setVisible(effects.size() != 0); + + int w=2; + if (adjustSize) + { + int s = effects.size() * 16+4; + int diff = parent->getWidth() - s; + parent->setSize(s, parent->getHeight()); + parent->setPosition(parent->getLeft()+diff, parent->getTop()); + } + + + for (std::map >::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + MyGUI::ImageBox* image; + if (mWidgetMap.find(it->first) == mWidgetMap.end()) + image = parent->createWidget + ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); + else + image = mWidgetMap[it->first]; + mWidgetMap[it->first] = image; + image->setPosition(w,2); + image->setVisible(true); + + const ESM::MagicEffect* effect = + MWBase::Environment::get().getWorld ()->getStore ().get().find(it->first); + + std::string icon = effect->mIcon; + icon[icon.size()-3] = 'd'; + icon[icon.size()-2] = 'd'; + icon[icon.size()-1] = 's'; + icon = "icons\\" + icon; + + image->setImageTexture(icon); + w += 16; + + float remainingDuration = 0; + + std::string sourcesDescription; + + const float fadeTime = 5.f; + + for (std::vector::const_iterator effectIt = it->second.begin(); + effectIt != it->second.end(); ++effectIt) + { + if (effectIt != it->second.begin()) + sourcesDescription += "\n"; + + // if at least one of the effect sources is permanent, the effect will never wear off + if (effectIt->mPermanent) + remainingDuration = fadeTime; + else + remainingDuration = std::max(remainingDuration, effectIt->mRemainingTime); + + sourcesDescription += effectIt->mSource; + + if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) + sourcesDescription += " (" + + MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Skill::sSkillNameIds[effectIt->mKey.mArg], "") + ")"; + if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + sourcesDescription += " (" + + MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Attribute::sGmstAttributeIds[effectIt->mKey.mArg], "") + ")"; + + if (!(effect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + std::string pt = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", ""); + std::string pts = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", ""); + + sourcesDescription += ": " + boost::lexical_cast(effectIt->mMagnitude); + sourcesDescription += " " + ((effectIt->mMagnitude > 1) ? pts : pt); + } + } + + std::string name = ESM::MagicEffect::effectIdToString (it->first); + + ToolTipInfo tooltipInfo; + tooltipInfo.caption = "#{" + name + "}"; + tooltipInfo.icon = effect->mIcon; + tooltipInfo.text = sourcesDescription; + tooltipInfo.imageSize = 16; + tooltipInfo.wordWrap = false; + + image->setUserData(tooltipInfo); + image->setUserString("ToolTipType", "ToolTipInfo"); + + // Fade out during the last 5 seconds + image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); + } + + // hide inactive effects + for (std::map::iterator it = mWidgetMap.begin(); it != mWidgetMap.end(); ++it) + { + if (effects.find(it->first) == effects.end()) + it->second->setVisible(false); + } + + } + + + std::string SpellIcons::getSpellDisplayName (const std::string& id) + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return spell->mName; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return potion->mName; + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return ingredient->mName; + + throw std::runtime_error ("ID " + id + " has no display name"); + } + + ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return spell->mEffects; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return potion->mEffects; + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + ingredient->mData.mEffectID[0]); + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[0]; + effect.mSkill = ingredient->mData.mSkills[0]; + effect.mAttribute = ingredient->mData.mAttributes[0]; + effect.mRange = 0; + effect.mArea = 0; + effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; + effect.mMagnMin = 1; + effect.mMagnMax = 1; + ESM::EffectList result; + result.mList.push_back (effect); + return result; + } + throw std::runtime_error("ID " + id + " does not have effects"); + } + +} diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp new file mode 100644 index 000000000..af600e347 --- /dev/null +++ b/apps/openmw/mwgui/spellicons.hpp @@ -0,0 +1,47 @@ +#ifndef MWGUI_SPELLICONS_H +#define MWGUI_SPELLICONS_H + +#include + +#include "../mwmechanics/magiceffects.hpp" + +namespace MyGUI +{ + class Widget; + class ImageBox; +} +namespace ESM +{ + struct ENAMstruct; + struct EffectList; +} + +namespace MWGui +{ + + // information about a single magic effect source as required for display in the tooltip + struct MagicEffectInfo + { + MagicEffectInfo() : mPermanent(false) {} + std::string mSource; // display name for effect source (e.g. potion name) + MWMechanics::EffectKey mKey; + int mMagnitude; + float mRemainingTime; + bool mPermanent; // the effect is permanent + }; + + class SpellIcons + { + public: + void updateWidgets(MyGUI::Widget* parent, bool adjustSize); + + private: + std::string getSpellDisplayName (const std::string& id); + ESM::EffectList getSpellEffectList (const std::string& id); + + std::map mWidgetMap; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index d62b23de4..50691d554 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -19,6 +19,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellsuccess.hpp" +#include "spellicons.hpp" #include "inventorywindow.hpp" #include "confirmationdialog.hpp" @@ -51,6 +52,8 @@ namespace MWGui , mHeight(0) , mWidth(0) { + mSpellIcons = new SpellIcons(); + getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); @@ -61,6 +64,11 @@ namespace MWGui mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); } + SpellWindow::~SpellWindow() + { + delete mSpellIcons; + } + void SpellWindow::onPinToggled() { mWindowManager.setSpellVisibility(!mPinned); @@ -73,6 +81,8 @@ namespace MWGui void SpellWindow::updateSpells() { + mSpellIcons->updateWidgets(mEffectBox, false); + const int spellHeight = 18; mHeight = 0; @@ -139,7 +149,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - spellList.push_back(*it); + spellList.push_back (it->first); } const MWWorld::ESMStore &esmStore = diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index caa67fd74..1963d4346 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -5,10 +5,13 @@ namespace MWGui { + class SpellIcons; + class SpellWindow : public WindowPinnableBase { public: SpellWindow(MWBase::WindowManager& parWindowManager); + virtual ~SpellWindow(); void updateSpells(); @@ -33,6 +36,8 @@ namespace MWGui virtual void onPinToggled(); virtual void open(); + + SpellIcons* mSpellIcons; }; } diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 70ceed857..0fa4127b5 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -67,7 +67,7 @@ StatsWindow::StatsWindow (MWBase::WindowManager& parWindowManager) for (int i = 0; i < ESM::Skill::Length; ++i) { mSkillValues.insert(std::pair >(i, MWMechanics::Stat())); - mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)nullptr)); + mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)NULL)); } MyGUI::WindowPtr t = static_cast(mMainWidget); @@ -419,7 +419,7 @@ void StatsWindow::updateSkillArea() { mChanged = false; - for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) + for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } diff --git a/apps/openmw/mwgui/stats_window.hpp b/apps/openmw/mwgui/stats_window.hpp index 6619680fa..3befc1f00 100644 --- a/apps/openmw/mwgui/stats_window.hpp +++ b/apps/openmw/mwgui/stats_window.hpp @@ -67,11 +67,11 @@ namespace MWGui SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::map > mSkillValues; std::map mSkillWidgetMap; - std::map mFactionWidgetMap; + std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank std::string mBirthSignId; int mReputation, mBounty; - std::vector mSkillWidgets; //< Skills and other information + std::vector mSkillWidgets; //< Skills and other information std::set mExpelled; bool mChanged; diff --git a/apps/openmw/mwgui/text_input.cpp b/apps/openmw/mwgui/text_input.cpp index 3dbe75165..9265cadf9 100644 --- a/apps/openmw/mwgui/text_input.cpp +++ b/apps/openmw/mwgui/text_input.cpp @@ -5,7 +5,7 @@ using namespace MWGui; TextInputDialog::TextInputDialog(MWBase::WindowManager& parWindowManager) - : WindowBase("openmw_text_input.layout", parWindowManager) + : WindowModal("openmw_text_input.layout", parWindowManager) { // Centre dialog center(); @@ -13,7 +13,7 @@ TextInputDialog::TextInputDialog(MWBase::WindowManager& parWindowManager) getWidget(mTextEdit, "TextEdit"); mTextEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted); - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); @@ -23,7 +23,7 @@ TextInputDialog::TextInputDialog(MWBase::WindowManager& parWindowManager) void TextInputDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr okButton; + MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) @@ -39,6 +39,7 @@ void TextInputDialog::setTextLabel(const std::string &label) void TextInputDialog::open() { + WindowModal::open(); // Make sure the edit box has focus MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } diff --git a/apps/openmw/mwgui/text_input.hpp b/apps/openmw/mwgui/text_input.hpp index 848310369..29de7388b 100644 --- a/apps/openmw/mwgui/text_input.hpp +++ b/apps/openmw/mwgui/text_input.hpp @@ -13,7 +13,7 @@ namespace MWGui namespace MWGui { - class TextInputDialog : public WindowBase + class TextInputDialog : public WindowModal { public: TextInputDialog(MWBase::WindowManager& parWindowManager); @@ -30,7 +30,7 @@ namespace MWGui void onTextAccepted(MyGUI::Edit* _sender); private: - MyGUI::EditPtr mTextEdit; + MyGUI::EditBox* mTextEdit; }; } #endif diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 97df211ac..8dcf398ca 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -80,9 +80,8 @@ void ToolTips::onFrame(float frameDuration) || (mWindowManager->getMode() == GM_Container) || (mWindowManager->getMode() == GM_Inventory))) { - std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); + mFocusObject = MWBase::Environment::get().getWorld()->getFacedObject(); - mFocusObject = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); if (mFocusObject.isEmpty ()) return; @@ -128,9 +127,7 @@ void ToolTips::onFrame(float frameDuration) Widget* focus = InputManager::getInstance().getMouseFocusWidget(); if (focus == 0) - { return; - } IntSize tooltipSize; @@ -169,6 +166,10 @@ void ToolTips::onFrame(float frameDuration) mFocusObject = *focus->getUserData(); tooltipSize = getToolTipViaPtr(false); } + else if (type == "ToolTipInfo") + { + tooltipSize = createToolTip(*focus->getUserData()); + } else if (type == "AvatarItemSelection") { MyGUI::IntCoord avatarPos = mWindowManager->getInventoryWindow ()->getAvatarScreenCoord (); @@ -364,7 +365,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) std::string caption = info.caption; std::string image = info.icon; - int imageSize = (image != "") ? 32 : 0; + int imageSize = (image != "") ? info.imageSize : 0; std::string text = info.text; // remove the first newline (easier this way) @@ -404,7 +405,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", IntCoord(0, 0, 300, 300), Align::Left | Align::Top, "ToolTipCaption"); captionWidget->setProperty("Static", "true"); - captionWidget->setCaption(caption); + captionWidget->setCaptionWithReplacing(caption); IntSize captionSize = captionWidget->getTextSize(); int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); @@ -412,7 +413,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), Align::Stretch, "ToolTipText"); textWidget->setProperty("Static", "true"); textWidget->setProperty("MultiLine", "true"); - textWidget->setProperty("WordWrap", "true"); + textWidget->setProperty("WordWrap", info.wordWrap ? "true" : "false"); textWidget->setCaptionWithReplacing(text); textWidget->setTextAlign(Align::HCenter | Align::Top); IntSize textSize = textWidget->getTextSize(); @@ -440,7 +441,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) effectsWidget->setWindowManager(mWindowManager); effectsWidget->setEffectList(info.effects); - std::vector effectItems; + std::vector effectItems; effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0); totalSize.height += coord.top-6; totalSize.width = std::max(totalSize.width, coord.width); @@ -460,7 +461,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) enchantWidget->setWindowManager(mWindowManager); enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects)); - std::vector enchantEffectItems; + std::vector enchantEffectItems; int flag = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, true, flag); totalSize.height += coord.top-6; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 4048d0d5a..ba94915cc 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -15,11 +15,14 @@ namespace MWGui public: ToolTipInfo() : isPotion(false) + , imageSize(32) + , wordWrap(true) {} std::string caption; std::string text; std::string icon; + int imageSize; // enchantment (for cloth, armor, weapons) std::string enchant; @@ -28,6 +31,7 @@ namespace MWGui Widgets::SpellEffectList effects; bool isPotion; // potions do not show target in the tooltip + bool wordWrap; }; class ToolTips : public OEngine::GUI::Layout diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index a005c618f..1b82d741c 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -21,11 +21,19 @@ namespace MWGui { + const float TradeWindow::sBalanceChangeInitialPause = 0.5; + const float TradeWindow::sBalanceChangeInterval = 0.1; + TradeWindow::TradeWindow(MWBase::WindowManager& parWindowManager) : WindowBase("openmw_trade_window.layout", parWindowManager) , ContainerBase(NULL) // no drag&drop , mCurrentBalance(0) + , mBalanceButtonsState(BBS_None) + , mBalanceChangePause(0.0) { + // items the NPC is wearing should not be for trade + mDisplayEquippedItems = true; + MyGUI::ScrollView* itemView; MyGUI::Widget* containerWidget; getWidget(containerWidget, "Items"); @@ -59,8 +67,11 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onCancelButtonClicked); mOfferButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onOfferButtonClicked); - mIncreaseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onIncreaseButtonClicked); - mDecreaseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onDecreaseButtonClicked); + mMaxSaleButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onMaxSaleButtonClicked); + mIncreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onIncreaseButtonPressed); + mIncreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); + mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onDecreaseButtonPressed); + mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); setCoord(400, 0, 400, 300); @@ -143,6 +154,21 @@ namespace MWGui } } + void TradeWindow::onFrame(float frameDuration) + { + if (!mMainWidget->getVisible() || mBalanceButtonsState == BBS_None) + return; + + mBalanceChangePause -= frameDuration; + if (mBalanceChangePause < 0.0) { + mBalanceChangePause += sBalanceChangeInterval; + if (mBalanceButtonsState == BBS_Increase) + onIncreaseButtonTriggered(); + else if (mBalanceButtonsState == BBS_Decrease) + onDecreaseButtonTriggered(); + } + } + void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) { const MWWorld::Store &gmst = @@ -169,21 +195,7 @@ namespace MWGui } // check if the merchant can afford this - int merchantgold; - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) - { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mNpdt52.mGold == -10) - merchantgold = ref->mBase->mNpdt12.mGold; - else - merchantgold = ref->mBase->mNpdt52.mGold; - } - else // ESM::Creature - { - MWWorld::LiveCellRef* ref = mPtr.get(); - merchantgold = ref->mBase->mData.mGold; - } - if (mCurrentBalance > 0 && merchantgold < mCurrentBalance) + if (mCurrentBalance > 0 && getMerchantGold() < mCurrentBalance) { // user notification MWBase::Environment::get().getWindowManager()-> @@ -242,7 +254,7 @@ namespace MWGui //skill use! MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0); - } + } int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition); @@ -271,14 +283,39 @@ namespace MWGui mWindowManager.removeGuiMode(GM_Barter); } - void TradeWindow::onIncreaseButtonClicked(MyGUI::Widget* _sender) + void TradeWindow::onMaxSaleButtonClicked(MyGUI::Widget* _sender) + { + mCurrentBalance = getMerchantGold(); + updateLabels(); + } + + void TradeWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + mBalanceButtonsState = BBS_Increase; + mBalanceChangePause = sBalanceChangeInitialPause; + onIncreaseButtonTriggered(); + } + + void TradeWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + mBalanceButtonsState = BBS_Decrease; + mBalanceChangePause = sBalanceChangeInitialPause; + onDecreaseButtonTriggered(); + } + + void TradeWindow::onBalanceButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) + { + mBalanceButtonsState = BBS_None; + } + + void TradeWindow::onIncreaseButtonTriggered() { if(mCurrentBalance<=-1) mCurrentBalance -= 1; if(mCurrentBalance>=1) mCurrentBalance += 1; updateLabels(); } - void TradeWindow::onDecreaseButtonClicked(MyGUI::Widget* _sender) + void TradeWindow::onDecreaseButtonTriggered() { if(mCurrentBalance<-1) mCurrentBalance += 1; if(mCurrentBalance>1) mCurrentBalance -= 1; @@ -300,46 +337,7 @@ namespace MWGui mTotalBalance->setCaption(boost::lexical_cast(-mCurrentBalance)); } - int merchantgold; - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) - { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mNpdt52.mGold == -10) - merchantgold = ref->mBase->mNpdt12.mGold; - else - merchantgold = ref->mBase->mNpdt52.mGold; - } - else // ESM::Creature - { - MWWorld::LiveCellRef* ref = mPtr.get(); - merchantgold = ref->mBase->mData.mGold; - } - - mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast(merchantgold)); - } - - std::vector TradeWindow::getEquippedItems() - { - std::vector items; - - if (mPtr.getTypeName() == typeid(ESM::Creature).name()) - { - // creatures don't have equipment slots. - return items; - } - - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); - if (it != invStore.end()) - { - items.push_back(*it); - } - } - - return items; + mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast(getMerchantGold())); } bool TradeWindow::npcAcceptsItem(MWWorld::Ptr item) @@ -401,19 +399,23 @@ namespace MWGui return items; } - void TradeWindow::sellToNpc(MWWorld::Ptr item, int count) + void TradeWindow::sellToNpc(MWWorld::Ptr item, int count, bool boughtItem) { + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, boughtItem); + + mCurrentBalance += diff; + mCurrentMerchantOffer += diff; - mCurrentBalance -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,true); - mCurrentMerchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,true); updateLabels(); } - void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count) + void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count, bool soldItem) { + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, !soldItem); + + mCurrentBalance -= diff; + mCurrentMerchantOffer -= diff; - mCurrentBalance += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,false); - mCurrentMerchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,false); updateLabels(); } @@ -423,4 +425,25 @@ namespace MWGui mWindowManager.removeGuiMode(GM_Barter); mWindowManager.removeGuiMode(GM_Dialogue); } + + int TradeWindow::getMerchantGold() + { + int merchantGold; + + if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + { + MWWorld::LiveCellRef* ref = mPtr.get(); + if (ref->mBase->mNpdt52.mGold == -10) + merchantGold = ref->mBase->mNpdt12.mGold; + else + merchantGold = ref->mBase->mNpdt52.mGold; + } + else // ESM::Creature + { + MWWorld::LiveCellRef* ref = mPtr.get(); + merchantGold = ref->mBase->mData.mGold; + } + + return merchantGold; + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index db386d8b6..2e05d03d5 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -27,14 +27,19 @@ namespace MWGui void startTrade(MWWorld::Ptr actor); - void sellToNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance - void buyFromNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance + void sellToNpc(MWWorld::Ptr item, int count, bool boughtItem); ///< only used for adjusting the gold balance + void buyFromNpc(MWWorld::Ptr item, int count, bool soldItem); ///< only used for adjusting the gold balance bool npcAcceptsItem(MWWorld::Ptr item); void addOrRemoveGold(int gold); + void onFrame(float frameDuration); + protected: + static const float sBalanceChangeInitialPause; // in seconds + static const float sBalanceChangeInterval; // in seconds + MyGUI::Button* mFilterAll; MyGUI::Button* mFilterWeapon; MyGUI::Button* mFilterApparel; @@ -57,16 +62,25 @@ namespace MWGui int mCurrentBalance; int mCurrentMerchantOffer; + enum BalanceButtonsState { + BBS_None, + BBS_Increase, + BBS_Decrease + } mBalanceButtonsState; + /// pause before next balance change will trigger while user holds +/- button pressed + float mBalanceChangePause; + void onWindowResize(MyGUI::Window* _sender); void onFilterChanged(MyGUI::Widget* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); void onCancelButtonClicked(MyGUI::Widget* _sender); - void onIncreaseButtonClicked(MyGUI::Widget* _sender); - void onDecreaseButtonClicked(MyGUI::Widget* _sender); + void onMaxSaleButtonClicked(MyGUI::Widget* _sender); + void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - // don't show items that the NPC has equipped in his trade-window. - virtual bool ignoreEquippedItems() { return true; } - virtual std::vector getEquippedItems(); + void onIncreaseButtonTriggered(); + void onDecreaseButtonTriggered(); virtual bool isTrading() { return true; } virtual bool isTradeWindow() { return true; } @@ -76,6 +90,9 @@ namespace MWGui void updateLabels(); virtual void onReferenceUnavailable(); + + private: + int getMerchantGold(); }; } diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index abbc6172f..465f588b8 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -82,7 +82,7 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing(travelId+" - "+boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", travelId); @@ -132,6 +132,8 @@ namespace MWGui if (mWindowManager.getInventoryWindow()->getPlayerGold()addOrRemoveGold (-price); + MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); ESM::Position pos = *_sender->getUserData(); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 4f2c98c08..794a9ab55 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -1,5 +1,7 @@ #include "waitdialog.hpp" +#include + #include #include @@ -14,6 +16,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "widgets.hpp" @@ -92,31 +95,46 @@ namespace MWGui // http://www.uesp.net/wiki/Lore:Calendar std::string month; int m = MWBase::Environment::get().getWorld ()->getMonth (); - if (m == 0) - month = "#{sMonthMorningstar}"; - else if (m == 1) - month = "#{sMonthSunsdawn}"; - else if (m == 2) - month = "#{sMonthFirstseed}"; - else if (m == 3) - month = "#{sMonthRainshand}"; - else if (m == 4) - month = "#{sMonthSecondseed}"; - else if (m == 5) - month = "#{sMonthMidyear}"; - else if (m == 6) - month = "#{sMonthSunsheight}"; - else if (m == 7) - month = "#{sMonthLastseed}"; - else if (m == 8) - month = "#{sMonthHeartfire}"; - else if (m == 9) - month = "#{sMonthFrostfall}"; - else if (m == 10) - month = "#{sMonthSunsdusk}"; - else if (m == 11) - month = "#{sMonthEveningstar}"; - + switch (m) { + case 0: + month = "#{sMonthMorningstar}"; + break; + case 1: + month = "#{sMonthSunsdawn}"; + break; + case 2: + month = "#{sMonthFirstseed}"; + break; + case 3: + month = "#{sMonthRainshand}"; + break; + case 4: + month = "#{sMonthSecondseed}"; + break; + case 5: + month = "#{sMonthMidyear}"; + break; + case 6: + month = "#{sMonthSunsheight}"; + break; + case 7: + month = "#{sMonthLastseed}"; + break; + case 8: + month = "#{sMonthHeartfire}"; + break; + case 9: + month = "#{sMonthFrostfall}"; + break; + case 10: + month = "#{sMonthSunsdusk}"; + break; + case 11: + month = "#{sMonthEveningstar}"; + break; + default: + break; + } int hour = MWBase::Environment::get().getWorld ()->getTimeStamp ().getHour (); bool pm = hour >= 12; if (hour >= 13) hour -= 12; @@ -132,21 +150,62 @@ namespace MWGui void WaitDialog::onUntilHealedButtonClicked(MyGUI::Widget* sender) { - startWaiting(); + // we need to sleep for a specific time, and since that isn't calculated yet, we'll do it here + // I'm making the assumption here that the # of hours rested is calculated when rest is started + // TODO: the rougher logic here (calculating the hourly deltas) should really go into helper funcs elsewhere + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats stats = MWWorld::Class::get(player).getCreatureStats(player); + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + float hourlyHealthDelta = stats.getAttribute(ESM::Attribute::Endurance).getModified() * 0.1; + + bool stunted = (stats.getMagicEffects().get(MWMechanics::EffectKey(136)).mMagnitude > 0); + float fRestMagicMult = store.get().find("fRestMagicMult")->getFloat(); + float hourlyMagickaDelta = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + // this massive duplication is why it has to be put into helper functions instead + float fFatigueReturnBase = store.get().find("fFatigueReturnBase")->getFloat(); + float fFatigueReturnMult = store.get().find("fFatigueReturnMult")->getFloat(); + float fEndFatigueMult = store.get().find("fEndFatigueMult")->getFloat(); + float capacity = MWWorld::Class::get(player).getCapacity(player); + float encumbrance = MWWorld::Class::get(player).getEncumbrance(player); + float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; + float hourlyFatigueDelta = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); + hourlyFatigueDelta *= 3600 * fEndFatigueMult * stats.getAttribute(ESM::Attribute::Endurance).getModified(); + + float healthHours = hourlyHealthDelta >= 0.0 + ? (stats.getHealth().getBase() - stats.getHealth().getCurrent()) / hourlyHealthDelta + : 1.0f; + float magickaHours = stunted ? 0.0 : + hourlyMagickaDelta >= 0.0 + ? (stats.getMagicka().getBase() - stats.getMagicka().getCurrent()) / hourlyMagickaDelta + : 1.0f; + float fatigueHours = hourlyFatigueDelta >= 0.0 + ? (stats.getFatigue().getBase() - stats.getFatigue().getCurrent()) / hourlyFatigueDelta + : 1.0f; + + int autoHours = int(std::ceil( std::max(std::max(healthHours, magickaHours), std::max(fatigueHours, 1.0f)) )); // this should use a variadic max if possible + + startWaiting(autoHours); } void WaitDialog::onWaitButtonClicked(MyGUI::Widget* sender) { - startWaiting(); + startWaiting(mManualHours); } - void WaitDialog::startWaiting () + void WaitDialog::startWaiting(int hoursToWait) { MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.2); setVisible(false); mProgressBar.setVisible (true); + mWaiting = true; mCurHour = 0; + mHours = hoursToWait; + mRemainingTime = 0.05; mProgressBar.setProgress (0, mHours); } @@ -159,7 +218,7 @@ namespace MWGui void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) { mHourText->setCaptionWithReplacing (boost::lexical_cast(position+1) + " #{sRestMenu2}"); - mHours = position+1; + mManualHours = position+1; } void WaitDialog::setCanRest (bool canRest) @@ -181,9 +240,9 @@ namespace MWGui mRemainingTime -= dt; - if (mRemainingTime < 0) + while (mRemainingTime < 0) { - mRemainingTime = 0.05; + mRemainingTime += 0.05; ++mCurHour; mProgressBar.setProgress (mCurHour, mHours); @@ -197,6 +256,7 @@ namespace MWGui if (mCurHour > mHours) stopWaiting(); + } void WaitDialog::stopWaiting () diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index 6af565c6e..c102d0fc6 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -47,6 +47,7 @@ namespace MWGui bool mSleeping; int mCurHour; int mHours; + int mManualHours; // stores the hours to rest selected via slider float mRemainingTime; WaitDialogProgressBar mProgressBar; @@ -58,7 +59,7 @@ namespace MWGui void setCanRest(bool canRest); - void startWaiting(); + void startWaiting(int hoursToWait); void stopWaiting(); }; diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 82e112826..506b90698 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -2,6 +2,9 @@ #include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -31,10 +34,10 @@ void MWGui::Widgets::fixTexturePath(std::string &path) /* MWSkill */ MWSkill::MWSkill() - : mManager(nullptr) + : mManager(NULL) , mSkillId(ESM::Skill::Length) - , mSkillNameWidget(nullptr) - , mSkillValueWidget(nullptr) + , mSkillNameWidget(NULL) + , mSkillValueWidget(NULL) { } @@ -103,7 +106,7 @@ void MWSkill::initialiseOverride() assignWidget(mSkillNameWidget, "StatName"); assignWidget(mSkillValueWidget, "StatValue"); - MyGUI::ButtonPtr button; + MyGUI::Button* button; assignWidget(button, "StatNameButton"); if (button) { @@ -123,10 +126,10 @@ void MWSkill::initialiseOverride() /* MWAttribute */ MWAttribute::MWAttribute() - : mManager(nullptr) + : mManager(NULL) , mId(-1) - , mAttributeNameWidget(nullptr) - , mAttributeValueWidget(nullptr) + , mAttributeNameWidget(NULL) + , mAttributeValueWidget(NULL) { } @@ -195,7 +198,7 @@ void MWAttribute::initialiseOverride() assignWidget(mAttributeNameWidget, "StatName"); assignWidget(mAttributeValueWidget, "StatValue"); - MyGUI::ButtonPtr button; + MyGUI::Button* button; assignWidget(button, "StatNameButton"); if (button) { @@ -215,8 +218,8 @@ void MWAttribute::initialiseOverride() /* MWSpell */ MWSpell::MWSpell() - : mWindowManager(nullptr) - , mSpellNameWidget(nullptr) + : mWindowManager(NULL) + , mSpellNameWidget(NULL) { } @@ -226,7 +229,7 @@ void MWSpell::setSpellId(const std::string &spellId) updateWidgets(); } -void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags) +void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -234,7 +237,7 @@ void MWSpell::createEffectWidgets(std::vector &effects, MyGUI: const ESM::Spell *spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - MWSpellEffectPtr effect = nullptr; + MWSpellEffectPtr effect = NULL; std::vector::const_iterator end = spell->mEffects.mList.end(); for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it) { @@ -286,7 +289,7 @@ MWSpell::~MWSpell() /* MWEffectList */ MWEffectList::MWEffectList() - : mWindowManager(nullptr) + : mWindowManager(NULL) , mEffectList(0) { } @@ -297,11 +300,11 @@ void MWEffectList::setEffectList(const SpellEffectList& list) updateWidgets(); } -void MWEffectList::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, bool center, int flags) +void MWEffectList::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags) { // We don't know the width of all the elements beforehand, so we do it in // 2 steps: first, create all widgets and check their width.... - MWSpellEffectPtr effect = nullptr; + MWSpellEffectPtr effect = NULL; int maxwidth = coord.width; for (SpellEffectList::iterator it=mEffectList.begin(); @@ -320,7 +323,7 @@ void MWEffectList::createEffectWidgets(std::vector &effects, M } // ... then adjust the size for all widgets - for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) { effect = static_cast(*it); bool needcenter = center && (maxwidth > effect->getRequestedWidth()); @@ -375,9 +378,9 @@ SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) /* MWSpellEffect */ MWSpellEffect::MWSpellEffect() - : mWindowManager(nullptr) - , mImageWidget(nullptr) - , mTextWidget(nullptr) + : mWindowManager(NULL) + , mImageWidget(NULL) + , mTextWidget(NULL) , mRequestedWidth(0) { } @@ -390,8 +393,13 @@ void MWSpellEffect::setSpellEffect(const SpellEffectParams& params) void MWSpellEffect::updateWidgets() { - if (!mWindowManager) + if (!mEffectParams.mKnown) + { + mTextWidget->setCaption ("?"); + mRequestedWidth = mTextWidget->getTextSize().width + 24; + mImageWidget->setImageTexture (""); return; + } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -400,7 +408,6 @@ void MWSpellEffect::updateWidgets() store.get().search(mEffectParams.mEffectID); assert(magicEffect); - assert(mWindowManager); std::string pt = mWindowManager->getGameSettingString("spoint", ""); std::string pts = mWindowManager->getGameSettingString("spoints", ""); @@ -417,17 +424,7 @@ void MWSpellEffect::updateWidgets() } if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) { - static const char *attributes[8] = { - "sAttributeStrength", - "sAttributeIntelligence", - "sAttributeWillpower", - "sAttributeAgility", - "sAttributeSpeed", - "sAttributeEndurance", - "sAttributePersonality", - "sAttributeLuck" - }; - spellLine += " " + mWindowManager->getGameSettingString(attributes[mEffectParams.mAttribute], ""); + spellLine += " " + mWindowManager->getGameSettingString(ESM::Attribute::sGmstAttributeIds[mEffectParams.mAttribute], ""); } if ((mEffectParams.mMagnMin >= 0 || mEffectParams.mMagnMax >= 0) && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) @@ -491,9 +488,9 @@ void MWSpellEffect::initialiseOverride() MWDynamicStat::MWDynamicStat() : mValue(0) , mMax(1) -, mTextWidget(nullptr) -, mBarWidget(nullptr) -, mBarTextWidget(nullptr) +, mTextWidget(NULL) +, mBarWidget(NULL) +, mBarTextWidget(NULL) { } diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 4ac5383f7..597bcbe32 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -2,11 +2,17 @@ #define MWGUI_WIDGETS_H #include "../mwworld/esmstore.hpp" - -#include - #include "../mwmechanics/stat.hpp" +#include +#include +#include + +namespace MyGUI +{ + class ImageBox; +} + namespace MWBase { class WindowManager; @@ -37,12 +43,15 @@ namespace MWGui , mEffectID(-1) , mNoTarget(false) , mIsConstant(false) + , mKnown(true) { } bool mNoTarget; // potion effects for example have no target (target is always the player) bool mIsConstant; // constant effect means that duration will not be displayed + bool mKnown; // is this effect known to the player? (If not, will display as a question mark instead) + // value of -1 here means the effect is unknown to the player short mEffectID; @@ -115,7 +124,8 @@ namespace MWGui MWBase::WindowManager *mManager; ESM::Skill::SkillEnum mSkillId; SkillValue mValue; - MyGUI::WidgetPtr mSkillNameWidget, mSkillValueWidget; + MyGUI::Widget* mSkillNameWidget; + MyGUI::Widget* mSkillValueWidget; }; typedef MWSkill* MWSkillPtr; @@ -157,7 +167,8 @@ namespace MWGui MWBase::WindowManager *mManager; int mId; AttributeValue mValue; - MyGUI::WidgetPtr mAttributeNameWidget, mAttributeValueWidget; + MyGUI::Widget* mAttributeNameWidget; + MyGUI::Widget* mAttributeValueWidget; }; typedef MWAttribute* MWAttributePtr; @@ -183,7 +194,7 @@ namespace MWGui * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. duration * @param various flags, see MWEffectList::EffectFlags */ - void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags); + void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags); const std::string &getSpellId() const { return mId; } @@ -227,7 +238,7 @@ namespace MWGui * @param center the effect widgets horizontally * @param various flags, see MWEffectList::EffectFlags */ - void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, bool center, int flags); + void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags); protected: virtual ~MWEffectList(); diff --git a/apps/openmw/mwgui/window_pinnable_base.cpp b/apps/openmw/mwgui/window_pinnable_base.cpp index 4ddf49d27..651b3a1e9 100644 --- a/apps/openmw/mwgui/window_pinnable_base.cpp +++ b/apps/openmw/mwgui/window_pinnable_base.cpp @@ -2,32 +2,27 @@ #include "../mwbase/windowmanager.hpp" +#include "exposedwindow.hpp" + using namespace MWGui; WindowPinnableBase::WindowPinnableBase(const std::string& parLayout, MWBase::WindowManager& parWindowManager) : WindowBase(parLayout, parWindowManager), mPinned(false), mVisible(false) { - MyGUI::WindowPtr t = static_cast(mMainWidget); - t->eventWindowButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onWindowButtonPressed); + ExposedWindow* window = static_cast(mMainWidget); + mPinButton = window->getSkinWidget ("Button"); + + mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked); } -void WindowPinnableBase::setVisible(bool b) +void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender) { - // Pinned windows can not be hidden - if (mPinned && !b) - return; + mPinned = !mPinned; - WindowBase::setVisible(b); - mVisible = b; -} - -void WindowPinnableBase::onWindowButtonPressed(MyGUI::Window* sender, const std::string& eventName) -{ - if ("PinToggle" == eventName) - { - mPinned = !mPinned; - onPinToggled(); - } - - eventDone(this); + if (mPinned) + mPinButton->changeWidgetSkin ("PinDown"); + else + mPinButton->changeWidgetSkin ("PinUp"); + + onPinToggled(); } diff --git a/apps/openmw/mwgui/window_pinnable_base.hpp b/apps/openmw/mwgui/window_pinnable_base.hpp index 250dde1f8..50259858e 100644 --- a/apps/openmw/mwgui/window_pinnable_base.hpp +++ b/apps/openmw/mwgui/window_pinnable_base.hpp @@ -11,15 +11,15 @@ namespace MWGui { public: WindowPinnableBase(const std::string& parLayout, MWBase::WindowManager& parWindowManager); - void setVisible(bool b); bool pinned() { return mPinned; } private: - void onWindowButtonPressed(MyGUI::Window* sender, const std::string& eventName); + void onPinButtonClicked(MyGUI::Widget* _sender); protected: virtual void onPinToggled() = 0; + MyGUI::Widget* mPinButton; bool mPinned; bool mVisible; }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 376eca88d..d866ec755 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -3,12 +3,13 @@ #include #include -#include "MyGUI_UString.h" +#include #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -50,13 +51,19 @@ #include "spellcreationdialog.hpp" #include "enchantingdialog.hpp" #include "trainingwindow.hpp" +#include "imagebutton.hpp" +#include "exposedwindow.hpp" +#include "cursor.hpp" +#include "spellicons.hpp" using namespace MWGui; WindowManager::WindowManager( - const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, - const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts) + const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *ogre, + const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage) : mGuiManager(NULL) + , mRendering(ogre) , mHud(NULL) , mMap(NULL) , mMenu(NULL) @@ -104,10 +111,10 @@ WindowManager::WindowManager( , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHudEnabled(true) + , mTranslationDataStorage (translationDataStorage) { - // Set up the GUI system - mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath); + mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); mGui = mGuiManager->getGui(); //Register own widgets with MyGUI @@ -123,6 +130,11 @@ WindowManager::WindowManager( MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + + MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); + MyGUI::ResourceManager::getInstance().load("core.xml"); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); @@ -167,13 +179,14 @@ WindowManager::WindowManager( mEnchantingDialog = new EnchantingDialog(*this); mTrainingWindow = new TrainingWindow(*this); - mLoadingScreen = new LoadingScreen(mOgre->getScene (), mOgre->getWindow (), *this); + mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow (), *this); mLoadingScreen->onResChange (w,h); mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); - // The HUD is always on - mHud->setVisible(true); + mCursor = new Cursor(); + + mHud->setVisible(mHudEnabled); mCharGen = new CharacterCreation(this); @@ -191,6 +204,9 @@ WindowManager::WindowManager( unsetSelectedSpell(); unsetSelectedWeapon(); + if (newGame) + disallowAll (); + // Set up visibility updateVisible(); } @@ -225,6 +241,9 @@ WindowManager::~WindowManager() delete mSpellCreationDialog; delete mEnchantingDialog; delete mTrainingWindow; + delete mCountDialog; + delete mQuickKeysMenu; + delete mCursor; cleanupGarbage(); @@ -251,6 +270,10 @@ void WindowManager::update() mHud->setFPS(mFPS); mHud->setTriangleCount(mTriangleCount); mHud->setBatchCount(mBatchCount); + + mHud->update(); + + mCursor->update(); } void WindowManager::updateVisible() @@ -279,10 +302,10 @@ void WindowManager::updateVisible() mEnchantingDialog->setVisible(false); mTrainingWindow->setVisible(false); - mHud->setVisible(true); + mHud->setVisible(mHudEnabled); // Mouse is visible whenever we're not in game mode - MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); + mCursor->setVisible(isGuiMode()); bool gameMode = !isGuiMode(); @@ -301,9 +324,16 @@ void WindowManager::updateVisible() setSpellVisibility((mAllowed & GW_Magic) && !mSpellWindow->pinned()); setHMSVisibility((mAllowed & GW_Stats) && !mStatsWindow->pinned()); - // If in game mode, don't show anything. + // If in game mode, show only the pinned windows if (gameMode) + { + mMap->setVisible(mMap->pinned()); + mStatsWindow->setVisible(mStatsWindow->pinned()); + mInventoryWindow->setVisible(mInventoryWindow->pinned()); + mSpellWindow->setVisible(mSpellWindow->pinned()); + return; + } GuiMode mode = mGuiModes.back(); @@ -318,6 +348,12 @@ void WindowManager::updateVisible() mSettingsWindow->setVisible(true); break; case GM_Console: + // Show the pinned windows + mMap->setVisible(mMap->pinned()); + mStatsWindow->setVisible(mStatsWindow->pinned()); + mInventoryWindow->setVisible(mInventoryWindow->pinned()); + mSpellWindow->setVisible(mSpellWindow->pinned()); + mConsole->enable(); break; case GM_Scroll: @@ -397,10 +433,20 @@ void WindowManager::updateVisible() break; case GM_LoadingWallpaper: mHud->setVisible(false); - MyGUI::PointerManager::getInstance().setVisible(false); + mCursor->setVisible(false); break; case GM_Loading: - MyGUI::PointerManager::getInstance().setVisible(false); + // Show the pinned windows + mMap->setVisible(mMap->pinned()); + mStatsWindow->setVisible(mStatsWindow->pinned()); + mInventoryWindow->setVisible(mInventoryWindow->pinned()); + mSpellWindow->setVisible(mSpellWindow->pinned()); + + mCursor->setVisible(false); + break; + case GM_Video: + mCursor->setVisible(false); + mHud->setVisible(false); break; default: // Unsupported mode, switch back to game @@ -534,10 +580,14 @@ void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) void WindowManager::messageBox (const std::string& message, const std::vector& buttons) { - if (buttons.empty()) - { - mMessageBoxManager->createMessageBox(message); + if(buttons.empty()){ + /* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */ + if(!mGuiModes.empty() && mGuiModes.back() == GM_Dialogue) + mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); + else + mMessageBoxManager->createMessageBox(message); } + else { mMessageBoxManager->createInteractiveMessageBox(message, buttons); @@ -545,6 +595,11 @@ void WindowManager::messageBox (const std::string& message, const std::vectorenterPressed(); +} + int WindowManager::readPressedButton () { return mMessageBoxManager->readPressedButton(); @@ -555,8 +610,9 @@ std::string WindowManager::getGameSettingString(const std::string &id, const std const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if (setting && setting->mType == ESM::VT_String) - return setting->getString(); + if (setting && setting->mValue.getType()==ESM::VT_String) + return setting->mValue.getString(); + return default_; } @@ -593,6 +649,7 @@ void WindowManager::onFrame (float frameDuration) mHud->onFrame(frameDuration); mTrainingWindow->onFrame (frameDuration); + mTradeWindow->onFrame(frameDuration); mTrainingWindow->checkReferenceAvailable(); mDialogueWindow->checkReferenceAvailable(); @@ -612,7 +669,7 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) if (cell->mCell->mName != "") { name = cell->mCell->mName; - mMap->addVisitedLocation (name, cell->mCell->getGridX (), cell->mCell->getGridY ()); + mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->mCell->getGridX (), cell->mCell->getGridY ()); } else { @@ -711,7 +768,7 @@ void WindowManager::setSpellVisibility(bool visible) void WindowManager::setMouseVisible(bool visible) { - MyGUI::PointerManager::getInstance().setVisible(visible); + mCursor->setVisible(visible); } void WindowManager::setDragDrop(bool dragDrop) @@ -722,13 +779,25 @@ void WindowManager::setDragDrop(bool dragDrop) void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().find(_tag); + std::string tag(_tag); - if (setting && setting->mType == ESM::VT_String) - _result = setting->getString(); + std::string tokenToFind = "sCell="; + size_t tokenLength = tokenToFind.length(); + + if (tag.substr(0, tokenLength) == tokenToFind) + { + _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); + } else - _result = _tag; + { + const ESM::GameSetting *setting = + MWBase::Environment::get().getWorld()->getStore().get().find(tag); + + if (setting && setting->mValue.getType()==ESM::VT_String) + _result = setting->mValue.getString(); + else + _result = tag; + } } void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) @@ -737,6 +806,7 @@ void WindowManager::processChangedSettings(const Settings::CategorySettingVector mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); bool changeRes = false; + bool windowRecreated = false; for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -746,6 +816,8 @@ void WindowManager::processChangedSettings(const Settings::CategorySettingVector { changeRes = true; } + else if (it->first == "Video" && it->second == "vsync") + windowRecreated = true; else if (it->first == "HUD" && it->second == "crosshair") mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); else if (it->first == "GUI" && it->second == "subtitles") @@ -769,6 +841,11 @@ void WindowManager::processChangedSettings(const Settings::CategorySettingVector mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); mInputBlocker->setSize(MyGUI::IntSize(x,y)); } + if (windowRecreated) + { + mGuiManager->updateWindow (mRendering->getWindow ()); + mLoadingScreen->updateWindow (mRendering->getWindow ()); + } } void WindowManager::pushGuiMode(GuiMode mode) @@ -910,12 +987,23 @@ bool WindowManager::isAllowed (GuiWindow wnd) const void WindowManager::allow (GuiWindow wnd) { mAllowed = (GuiWindow)(mAllowed | wnd); + + if (wnd & GW_Inventory) + { + mBookWindow->setInventoryAllowed (true); + mScrollWindow->setInventoryAllowed (true); + } + updateVisible(); } void WindowManager::disallowAll() { mAllowed = GW_None; + + mBookWindow->setInventoryAllowed (false); + mScrollWindow->setInventoryAllowed (false); + updateVisible(); } @@ -974,7 +1062,6 @@ void WindowManager::notifyInputActionBound () allowMouse(); } - void WindowManager::showCrosshair (bool show) { mHud->setCrosshairVisible (show && mCrosshairEnabled); @@ -1035,3 +1122,13 @@ void WindowManager::startTraining(MWWorld::Ptr actor) { mTrainingWindow->startTraining(actor); } + +const Translation::Storage& WindowManager::getTranslationDataStorage() const +{ + return mTranslationDataStorage; +} + +void WindowManager::changePointer(const std::string &name) +{ + mCursor->onCursorChange(name); +} diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 2e684b5da..122b10cc3 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -29,6 +29,11 @@ namespace Compiler class Extensions; } +namespace Translation +{ + class Storage; +} + namespace OEngine { namespace GUI @@ -67,6 +72,8 @@ namespace MWGui class SpellCreationDialog; class EnchantingDialog; class TrainingWindow; + class Cursor; + class SpellIcons; class WindowManager : public MWBase::WindowManager { @@ -76,7 +83,8 @@ namespace MWGui WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, - const std::string& cacheDir, bool consoleOnlyScripts); + const std::string& cacheDir, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage); virtual ~WindowManager(); /** @@ -182,7 +190,8 @@ namespace MWGui virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. - virtual void messageBox (const std::string& message, const std::vector& buttons); + virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector()); + virtual void enterPressed (); virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration); @@ -219,8 +228,13 @@ namespace MWGui virtual void startEnchanting(MWWorld::Ptr actor); virtual void startTraining(MWWorld::Ptr actor); + virtual void changePointer (const std::string& name); + + virtual const Translation::Storage& getTranslationDataStorage() const; + private: OEngine::GUI::MyGUIManager *mGuiManager; + OEngine::Render::OgreRenderer *mRendering; HUD *mHud; MapWindow *mMap; MainMenu *mMenu; @@ -250,6 +264,8 @@ namespace MWGui SpellCreationDialog* mSpellCreationDialog; EnchantingDialog* mEnchantingDialog; TrainingWindow* mTrainingWindow; + Translation::Storage& mTranslationDataStorage; + Cursor* mCursor; CharacterCreation* mCharGen; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 6a1c3aa9b..f18c02a0e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -21,6 +21,7 @@ #include "../engine.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -40,9 +41,12 @@ namespace MWInput , mMouseLookEnabled(true) , mMouseX(ogre.getWindow()->getWidth ()/2.f) , mMouseY(ogre.getWindow()->getHeight ()/2.f) - , mUserFile(userFile) + , mMouseWheel(0) , mDragDrop(false) , mGuiCursorEnabled(false) + , mDebug(debug) + , mUserFile(userFile) + , mUserFileExists(userFileExists) , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input")) @@ -50,8 +54,9 @@ namespace MWInput , mUIYMultiplier (Settings::Manager::getFloat("ui y multiplier", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) + , mOverencumberedMessageDelay(0.f) { - Ogre::RenderWindow* window = ogre.getWindow (); + Ogre::RenderWindow* window = mOgre.getWindow (); size_t windowHnd; resetIdleTime(); @@ -66,7 +71,7 @@ namespace MWInput // Set non-exclusive mouse and keyboard input if the user requested // it. - if (debug) + if (mDebug) { #if defined OIS_WIN32_PLATFORM pl.insert(std::make_pair(std::string("w32_mouse"), @@ -84,10 +89,12 @@ namespace MWInput std::string("false"))); pl.insert(std::make_pair(std::string("x11_keyboard_grab"), std::string("false"))); - pl.insert(std::make_pair(std::string("XAutoRepeatOn"), - std::string("true"))); #endif } +#if defined OIS_LINUX_PLATFORM + pl.insert(std::make_pair(std::string("XAutoRepeatOn"), + std::string("true"))); +#endif #if defined(__APPLE__) && !defined(__LP64__) // Give the application window focus to receive input events @@ -111,7 +118,7 @@ namespace MWInput MyGUI::InputManager::getInstance().injectMouseMove(mMouseX, mMouseY, mMouse->getMouseState ().Z.abs); - std::string file = userFileExists ? userFile : ""; + std::string file = mUserFileExists ? mUserFile : ""; mInputCtrl = new ICS::InputControlSystem(file, true, this, NULL, A_Last); loadKeyDefaults(); @@ -174,6 +181,11 @@ namespace MWInput case A_Activate: resetIdleTime(); activate(); + if( MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_InterMessageBox ) { + // Pressing the activation key when a messagebox is prompting for "ok" will activate the ok button + MWBase::Environment::get().getWindowManager()->enterPressed(); + } break; case A_Journal: toggleJournal (); @@ -181,9 +193,6 @@ namespace MWInput case A_AutoMove: toggleAutoMove (); break; - case A_ToggleSneak: - /// \todo implement - break; case A_ToggleWalk: toggleWalking (); break; @@ -232,7 +241,7 @@ namespace MWInput case A_ToggleHUD: mWindows.toggleHud(); break; - } + } } } @@ -242,10 +251,15 @@ namespace MWInput mKeyboard->capture(); mMouse->capture(); + // inject some fake mouse movement to force updating MyGUI's widget states + // this shouldn't do any harm since we're moving back to the original position afterwards + MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX+1), int(mMouseY+1), mMouseWheel); + MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); + // update values of channels (as a result of pressed keys) if (!loading) mInputCtrl->update(dt); - + // Update windows/gui as a result of input events // For instance this could mean opening a new window/dialog, // by doing this after the input events are handled we @@ -262,39 +276,65 @@ namespace MWInput // be done in the physics system. if (mControlSwitch["playercontrols"]) { + bool triedToMove = false; if (actionIsActive(A_MoveLeft)) { - mPlayer.setAutoMove (false); - mPlayer.setLeftRight (1); + triedToMove = true; + mPlayer.setLeftRight (-1); } else if (actionIsActive(A_MoveRight)) { - mPlayer.setAutoMove (false); - mPlayer.setLeftRight (-1); + triedToMove = true; + mPlayer.setLeftRight (1); } else mPlayer.setLeftRight (0); if (actionIsActive(A_MoveForward)) { + triedToMove = true; mPlayer.setAutoMove (false); mPlayer.setForwardBackward (1); } else if (actionIsActive(A_MoveBackward)) { + triedToMove = true; mPlayer.setAutoMove (false); mPlayer.setForwardBackward (-1); } else mPlayer.setForwardBackward (0); + mPlayer.setSneak(actionIsActive(A_Sneak)); + if (actionIsActive(A_Jump) && mControlSwitch["playerjumping"]) + { mPlayer.setUpDown (1); - else if (actionIsActive(A_Crouch)) - mPlayer.setUpDown (-1); + triedToMove = true; + } else mPlayer.setUpDown (0); + if(actionIsActive(A_Run)) + mPlayer.setRunState(true); + else + mPlayer.setRunState(false); + + // if player tried to start moving, but can't (due to being overencumbered), display a notification. + if (triedToMove) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + mOverencumberedMessageDelay -= dt; + if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player)) + { + if (mOverencumberedMessageDelay <= 0) + { + MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage59}"); + mOverencumberedMessageDelay = 1.0; + } + } + } + if (mControlSwitch["playerviewswitch"]) { // work around preview mode toggle when pressing Alt+Tab @@ -321,7 +361,7 @@ namespace MWInput actionIsActive(A_MoveLeft) || actionIsActive(A_MoveRight) || actionIsActive(A_Jump) || - actionIsActive(A_Crouch) || + actionIsActive(A_Sneak) || actionIsActive(A_TogglePOV)) { resetIdleTime(); @@ -420,6 +460,14 @@ namespace MWInput bool InputManager::keyPressed( const OIS::KeyEvent &arg ) { + if(arg.key == OIS::KC_RETURN + && MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_InterMessageBox ) + { + // Pressing enter when a messagebox is prompting for "ok" will activate the ok button + MWBase::Environment::get().getWindowManager()->enterPressed(); + } + mInputCtrl->keyPressed (arg); unsigned int text = arg.text; #ifdef __APPLE__ // filter \016 symbol for F-keys on OS X @@ -486,8 +534,9 @@ namespace MWInput mMouseY += float(arg.state.Y.rel) * mUISensitivity * mUIYMultiplier; mMouseX = std::max(0.f, std::min(mMouseX, float(viewSize.width))); mMouseY = std::max(0.f, std::min(mMouseY, float(viewSize.height))); + mMouseWheel = arg.state.Z.abs; - MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), arg.state.Z.abs); + MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); } if (mMouseLookEnabled) @@ -499,6 +548,9 @@ namespace MWInput MWBase::World *world = MWBase::Environment::get().getWorld(); world->rotateObject(world->getPlayer().getPlayer(), -y, 0.f, x, true); + + if (arg.state.Z.rel) + MWBase::Environment::get().getWorld()->changeVanityModeScale(arg.state.Z.rel); } return true; @@ -506,8 +558,13 @@ namespace MWInput void InputManager::toggleMainMenu() { + if (MyGUI::InputManager::getInstance ().isModalAny()) + return; + if (mWindows.isGuiMode () && (mWindows.getMode () == MWGui::GM_MainMenu || mWindows.getMode () == MWGui::GM_Settings)) mWindows.popGuiMode(); + else if (mWindows.isGuiMode () && mWindows.getMode () == MWGui::GM_Video) + MWBase::Environment::get().getWorld ()->stopVideo (); else mWindows.pushGuiMode (MWGui::GM_MainMenu); } @@ -570,14 +627,21 @@ namespace MWInput // Toggle between game mode and inventory mode if(gameMode) mWindows.pushGuiMode(MWGui::GM_Inventory); - else if(mWindows.getMode() == MWGui::GM_Inventory) - mWindows.popGuiMode(); + else + { + MWGui::GuiMode mode = mWindows.getMode(); + if(mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) + mWindows.popGuiMode(); + } - // .. but don't touch any other mode. + // .. but don't touch any other mode, except container. } void InputManager::toggleConsole() { + if (MyGUI::InputManager::getInstance ().isModalAny()) + return; + bool gameMode = !mWindows.isGuiMode(); // Switch to console mode no matter what mode we are currently @@ -614,11 +678,14 @@ namespace MWInput { if (!mWindows.isGuiMode ()) mWindows.pushGuiMode (MWGui::GM_QuickKeysMenu); + else if (mWindows.getMode () == MWGui::GM_QuickKeysMenu) + mWindows.removeGuiMode (MWGui::GM_QuickKeysMenu); } void InputManager::activate() { - mEngine.activate(); + if (mControlSwitch["playercontrols"]) + mEngine.activate(); } void InputManager::toggleAutoMove() @@ -681,7 +748,8 @@ namespace MWInput defaultKeyBindings[A_ToggleSpell] = OIS::KC_R; defaultKeyBindings[A_QuickKeysMenu] = OIS::KC_F1; defaultKeyBindings[A_Console] = OIS::KC_F2; - defaultKeyBindings[A_Crouch] = OIS::KC_LCONTROL; + defaultKeyBindings[A_Run] = OIS::KC_LSHIFT; + defaultKeyBindings[A_Sneak] = OIS::KC_LCONTROL; defaultKeyBindings[A_AutoMove] = OIS::KC_Q; defaultKeyBindings[A_Jump] = OIS::KC_E; defaultKeyBindings[A_Journal] = OIS::KC_J; @@ -747,7 +815,8 @@ namespace MWInput descriptions[A_ToggleWeapon] = "sReady_Weapon"; descriptions[A_ToggleSpell] = "sReady_Magic"; descriptions[A_Console] = "sConsoleTitle"; - descriptions[A_Crouch] = "sCrouch_Sneak"; + descriptions[A_Run] = "sRun"; + descriptions[A_Sneak] = "sCrouch_Sneak"; descriptions[A_AutoMove] = "sAuto_Run"; descriptions[A_Jump] = "sJump"; descriptions[A_Journal] = "sJournal"; @@ -795,7 +864,8 @@ namespace MWInput ret.push_back(A_MoveLeft); ret.push_back(A_MoveRight); ret.push_back(A_TogglePOV); - ret.push_back(A_Crouch); + ret.push_back(A_Run); + ret.push_back(A_Sneak); ret.push_back(A_Activate); ret.push_back(A_ToggleWeapon); ret.push_back(A_ToggleSpell); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 718d6b76f..8bb20b7be 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -145,8 +145,13 @@ namespace MWInput bool mMouseLookEnabled; bool mGuiCursorEnabled; + float mOverencumberedMessageDelay; + float mMouseX; float mMouseY; + int mMouseWheel; + bool mDebug; + bool mUserFileExists; std::map mControlSwitch; @@ -206,14 +211,14 @@ namespace MWInput A_Journal, //Journal A_Weapon, //Draw/Sheath weapon A_Spell, //Ready/Unready Casting - A_AlwaysRun, //Toggle Always Run + A_Run, //Run when held A_CycleSpellLeft, //cycling through spells A_CycleSpellRight, A_CycleWeaponLeft,//Cycling through weapons A_CycleWeaponRight, - A_ToggleSneak, //Toggles Sneak, add Push-Sneak later + A_ToggleSneak, //Toggles Sneak A_ToggleWalk, //Toggle Walking/Running - A_Crouch, + A_Sneak, A_QuickSave, A_QuickLoad, diff --git a/apps/openmw/mwmechanics/activators.cpp b/apps/openmw/mwmechanics/activators.cpp new file mode 100644 index 000000000..b67fcb216 --- /dev/null +++ b/apps/openmw/mwmechanics/activators.cpp @@ -0,0 +1,76 @@ +#include "activators.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +namespace MWMechanics +{ + +Activators::Activators() +{ +} + +void Activators::addActivator(const MWWorld::Ptr& ptr) +{ + MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if(anim != NULL) + mActivators.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Idle, true))); +} + +void Activators::removeActivator (const MWWorld::Ptr& ptr) +{ + PtrControllerMap::iterator iter = mActivators.find(ptr); + if(iter != mActivators.end()) + mActivators.erase(iter); +} + +void Activators::updateActivator(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) +{ + PtrControllerMap::iterator iter = mActivators.find(old); + if(iter != mActivators.end()) + { + CharacterController ctrl = iter->second; + mActivators.erase(iter); + + ctrl.updatePtr(ptr); + mActivators.insert(std::make_pair(ptr, ctrl)); + } +} + +void Activators::dropActivators (const MWWorld::Ptr::CellStore *cellStore) +{ + PtrControllerMap::iterator iter = mActivators.begin(); + while(iter != mActivators.end()) + { + if(iter->first.getCell()==cellStore) + mActivators.erase(iter++); + else + ++iter; + } +} + +void Activators::update(float duration, bool paused) +{ + if(!paused) + { + for(PtrControllerMap::iterator iter(mActivators.begin());iter != mActivators.end();++iter) + iter->second.update(duration); + } +} + +void Activators::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) +{ + PtrControllerMap::iterator iter = mActivators.find(ptr); + if(iter != mActivators.end()) + iter->second.playGroup(groupName, mode, number); +} +void Activators::skipAnimation(const MWWorld::Ptr& ptr) +{ + PtrControllerMap::iterator iter = mActivators.find(ptr); + if(iter != mActivators.end()) + iter->second.skipAnim(); +} + +} diff --git a/apps/openmw/mwmechanics/activators.hpp b/apps/openmw/mwmechanics/activators.hpp new file mode 100644 index 000000000..137674a57 --- /dev/null +++ b/apps/openmw/mwmechanics/activators.hpp @@ -0,0 +1,45 @@ +#ifndef GAME_MWMECHANICS_ACTIVATORS_H +#define GAME_MWMECHANICS_ACTOVATRS_H + +#include +#include + +#include "character.hpp" + +namespace MWWorld +{ + class Ptr; + class CellStore; +} + +namespace MWMechanics +{ + class Activators + { + typedef std::map PtrControllerMap; + PtrControllerMap mActivators; + + public: + Activators(); + + void addActivator (const MWWorld::Ptr& ptr); + ///< Register an animated activator + + void removeActivator (const MWWorld::Ptr& ptr); + ///< Deregister an activator + + void updateActivator(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); + ///< Updates an activator with a new Ptr + + void dropActivators (const MWWorld::CellStore *cellStore); + ///< Deregister all activators in the given cell. + + void update (float duration, bool paused); + ///< Update activator animations + + void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + void skipAnimation(const MWWorld::Ptr& ptr); + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index f53ccdce3..9aca6b7b7 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -62,7 +62,7 @@ namespace MWMechanics for (TIterator iter (begin()); iter!=end(); ++iter) { - std::pair effects = getEffectList (iter->first); + std::pair > effects = getEffectList (iter->first); const MWWorld::TimeStamp& start = iter->second.first; float magnitude = iter->second.second; @@ -74,7 +74,7 @@ namespace MWMechanics { int duration = iter->mDuration; - if (effects.second) + if (effects.second.first) duration *= magnitude; MWWorld::TimeStamp end = start; @@ -85,7 +85,7 @@ namespace MWMechanics { EffectParam param; - if (effects.second) + if (effects.second.first) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( @@ -113,15 +113,15 @@ namespace MWMechanics } } - std::pair ActiveSpells::getEffectList (const std::string& id) const + std::pair > ActiveSpells::getEffectList (const std::string& id) const { if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (spell->mEffects, false); + return std::make_pair (spell->mEffects, std::make_pair(false, false)); if (const ESM::Potion *potion = MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (potion->mEffects, false); + return std::make_pair (potion->mEffects, std::make_pair(false, true)); if (const ESM::Ingredient *ingredient = MWBase::Environment::get().getWorld()->getStore().get().search (id)) @@ -140,11 +140,12 @@ namespace MWMechanics effect.mMagnMin = 1; effect.mMagnMax = 1; - std::pair result; - + std::pair > result; + result.second.second = true; + result.second.first = true; + result.first.mList.push_back (effect); - result.second = true; - + return result; } @@ -157,7 +158,8 @@ namespace MWMechanics bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor) { - std::pair effects = getEffectList (id); + std::pair > effects = getEffectList (id); + bool stacks = effects.second.second; bool found = false; @@ -178,7 +180,7 @@ namespace MWMechanics float random = static_cast (std::rand()) / RAND_MAX; - if (effects.second) + if (effects.second.first) { // ingredient -> special treatment required. const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); @@ -194,7 +196,7 @@ namespace MWMechanics random *= 0.25 * x; } - if (iter==mSpells.end()) + if (iter==mSpells.end() || stacks) mSpells.insert (std::make_pair (id, std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random))); else @@ -236,7 +238,7 @@ namespace MWMechanics double ActiveSpells::timeToExpire (const TIterator& iterator) const { - std::pair effects = getEffectList (iterator->first); + std::pair > effects = getEffectList (iterator->first); int duration = 0; @@ -247,7 +249,7 @@ namespace MWMechanics duration = iter->mDuration; } - if (effects.second) + if (effects.second.first) duration *= iterator->second.second; double scaledDuration = duration * @@ -263,15 +265,20 @@ namespace MWMechanics bool ActiveSpells::isSpellActive(std::string id) const { - boost::algorithm::to_lower(id); + Misc::StringUtils::toLower(id); for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { std::string left = iter->first; - boost::algorithm::to_lower(left); + Misc::StringUtils::toLower(left); if (iter->first == id) return true; } return false; } + + const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const + { + return mSpells; + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 6b832f4cd..8c859b2cb 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -30,7 +30,7 @@ namespace MWMechanics { public: - typedef std::map > TContainer; + typedef std::multimap > TContainer; typedef TContainer::const_iterator TIterator; private: @@ -44,7 +44,8 @@ namespace MWMechanics void rebuildEffects() const; - std::pair getEffectList (const std::string& id) const; + std::pair > getEffectList (const std::string& id) const; + ///< @return (EffectList, (isIngredient, stacks)) public: @@ -63,6 +64,8 @@ namespace MWMechanics const MagicEffects& getMagicEffects() const; + const TContainer& getActiveSpells() const; + TIterator begin() const; TIterator end() const; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d541baea9..9632bdf76 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -165,35 +165,46 @@ namespace MWMechanics void Actors::addActor (const MWWorld::Ptr& ptr) { - if (!MWWorld::Class::get (ptr).getCreatureStats (ptr).isDead()) - mActors.insert (ptr); + MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if(!MWWorld::Class::get(ptr).getCreatureStats(ptr).isDead()) + mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Idle, true))); else - MWBase::Environment::get().getWorld()->playAnimationGroup (ptr, "death1", 2); + mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Death1, false))); } void Actors::removeActor (const MWWorld::Ptr& ptr) { - std::set::iterator iter = mActors.find (ptr); + PtrControllerMap::iterator iter = mActors.find(ptr); + if(iter != mActors.end()) + mActors.erase(iter); + } - if (iter!=mActors.end()) - mActors.erase (iter); + void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) + { + PtrControllerMap::iterator iter = mActors.find(old); + if(iter != mActors.end()) + { + CharacterController ctrl = iter->second; + mActors.erase(iter); + + ctrl.updatePtr(ptr); + mActors.insert(std::make_pair(ptr, ctrl)); + } } void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) { - std::set::iterator iter = mActors.begin(); - - while (iter!=mActors.end()) - if (iter->getCell()==cellStore) - { - mActors.erase (iter++); - } + PtrControllerMap::iterator iter = mActors.begin(); + while(iter != mActors.end()) + { + if(iter->first.getCell()==cellStore) + mActors.erase(iter++); else ++iter; + } } - void Actors::update (std::vector >& movement, float duration, - bool paused) + void Actors::update (float duration, bool paused) { mDuration += duration; @@ -201,79 +212,91 @@ namespace MWMechanics { float totalDuration = mDuration; mDuration = 0; - - std::set::iterator iter (mActors.begin()); - while (iter!=mActors.end()) + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { - if (!MWWorld::Class::get (*iter).getCreatureStats (*iter).isDead()) + if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) { - updateActor (*iter, totalDuration); + if(iter->second.getState() >= CharState_Death1) + iter->second.setState(CharState_Idle, true); - if (iter->getTypeName()==typeid (ESM::NPC).name()) - updateNpc (*iter, totalDuration, paused); + updateActor(iter->first, totalDuration); + if(iter->first.getTypeName() == typeid(ESM::NPC).name()) + updateNpc(iter->first, totalDuration, paused); + + if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) + continue; } - if (MWWorld::Class::get (*iter).getCreatureStats (*iter).isDead()) + // workaround: always keep player alive for now + // \todo remove workaround, once player death can be handled + if(iter->first.getRefData().getHandle()=="player") { - // workaround: always keep player alive for now - // \todo remove workaround, once player death can be handled - if (iter->getRefData().getHandle()=="player") - { - MWMechanics::DynamicStat stat ( - MWWorld::Class::get (*iter).getCreatureStats (*iter).getHealth()); - - if (stat.getModified()<1) - { - stat.setModified (1, 0); - MWWorld::Class::get (*iter).getCreatureStats (*iter).setHealth (stat); - } + MWMechanics::DynamicStat stat ( + MWWorld::Class::get(iter->first).getCreatureStats(iter->first).getHealth()); - MWWorld::Class::get (*iter).getCreatureStats (*iter).resurrect(); - ++iter; - continue; + if (stat.getModified()<1) + { + stat.setModified (1, 0); + MWWorld::Class::get(iter->first).getCreatureStats(iter->first).setHealth(stat); } - ++mDeathCount[MWWorld::Class::get (*iter).getId (*iter)]; - - MWBase::Environment::get().getWorld()->playAnimationGroup (*iter, "death1", 0); - - if (MWWorld::Class::get (*iter).isEssential (*iter)) - MWBase::Environment::get().getWindowManager()->messageBox ( - "#{sKilledEssential}", std::vector()); - - mActors.erase (iter++); + MWWorld::Class::get(iter->first).getCreatureStats(iter->first).resurrect(); + continue; } - else - ++iter; + + if(iter->second.getState() >= CharState_Death1) + continue; + + iter->second.setState(CharState_Death1, false); + + ++mDeathCount[MWWorld::Class::get(iter->first).getId(iter->first)]; + + if(MWWorld::Class::get(iter->first).isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox( + "#{sKilledEssential}", std::vector()); } } - for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); - ++iter) + if(!paused) { - Ogre::Vector3 vector = MWWorld::Class::get (*iter).getMovementVector (*iter); + mMovement.reserve(mActors.size()); - if (vector!=Ogre::Vector3::ZERO) - movement.push_back (std::make_pair (iter->getRefData().getHandle(), vector)); + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + Ogre::Vector3 movement = iter->second.update(duration); + mMovement.push_back(std::make_pair(iter->first, movement)); + } + MWBase::Environment::get().getWorld()->doPhysics(mMovement, duration); + + mMovement.clear(); } } void Actors::restoreDynamicStats() { - for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); ++iter) - { - calculateRestoration (*iter, 3600); - } + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + calculateRestoration(iter->first, 3600); } int Actors::countDeaths (const std::string& id) const { - std::map::const_iterator iter = mDeathCount.find (id); - - if (iter!=mDeathCount.end()) + std::map::const_iterator iter = mDeathCount.find(id); + if(iter != mDeathCount.end()) return iter->second; - return 0; } + + void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + { + PtrControllerMap::iterator iter = mActors.find(ptr); + if(iter != mActors.end()) + iter->second.playGroup(groupName, mode, number); + } + void Actors::skipAnimation(const MWWorld::Ptr& ptr) + { + PtrControllerMap::iterator iter = mActors.find(ptr); + if(iter != mActors.end()) + iter->second.skipAnim(); + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 79ae16fc3..fc4af8dd6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -6,6 +6,9 @@ #include #include +#include "character.hpp" +#include "../mwbase/world.hpp" + namespace Ogre { class Vector3; @@ -21,9 +24,14 @@ namespace MWMechanics { class Actors { - std::set mActors; - float mDuration; - std::map mDeathCount; + typedef std::map PtrControllerMap; + PtrControllerMap mActors; + + MWWorld::PtrMovementList mMovement; + + std::map mDeathCount; + + float mDuration; void updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused); @@ -50,11 +58,13 @@ namespace MWMechanics /// /// \note Ignored, if \a ptr is not a registered actor. + void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); + ///< Updates an actor with a new Ptr + void dropActors (const MWWorld::CellStore *cellStore); ///< Deregister all actors in the given cell. - void update (std::vector >& movement, - float duration, bool paused); + void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement void updateActor (const MWWorld::Ptr& ptr, float duration); @@ -66,6 +76,9 @@ namespace MWMechanics int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. + + void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + void skipAnimation(const MWWorld::Ptr& ptr); }; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 27cd9095d..ebbea55b0 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -5,6 +5,12 @@ MWMechanics::AiEscort::AiEscort(const std::string &actorId,int duration, float x : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) { } + +MWMechanics::AiEscort::AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z) +: mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) +{ +} + MWMechanics::AiEscort *MWMechanics::AiEscort::clone() const { diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index fef70f508..d89a9586c 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -10,6 +10,10 @@ namespace MWMechanics { public: AiEscort(const std::string &actorId,int duration, float x, float y, float z); + ///< \implement AiEscort + AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + ///< \implement AiEscortCell + virtual AiEscort *clone() const; virtual bool execute (const MWWorld::Ptr& actor); @@ -19,6 +23,7 @@ namespace MWMechanics private: std::string mActorId; + std::string mCellId; float mX; float mY; float mZ; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 3fee6d98c..dab9e0283 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -5,6 +5,11 @@ MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float : mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId) { } +MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) +: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) +{ +} + MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const { return new AiFollow(*this); diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index ded13d780..0b37b0a2d 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -11,6 +11,7 @@ namespace MWMechanics { public: AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); + AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor); ///< \return Package completed? @@ -21,7 +22,8 @@ namespace MWMechanics float mX; float mY; float mZ; - std::string mActorId; + std::string mActorId; + std::string mCellId; }; } #endif diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp new file mode 100644 index 000000000..62958db8d --- /dev/null +++ b/apps/openmw/mwmechanics/character.cpp @@ -0,0 +1,302 @@ +/* + * OpenMW - The completely unofficial reimplementation of Morrowind + * + * This file (character.cpp) is part of the OpenMW package. + * + * OpenMW is distributed as free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 3 along with this program. If not, see + * http://www.gnu.org/licenses/ . + */ + +#include "character.hpp" + +#include + +#include "../mwrender/animation.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" + + +namespace MWMechanics +{ + +static const struct { + CharacterState state; + const char groupname[32]; +} sStateList[] = { + { CharState_Idle, "idle" }, + { CharState_Idle2, "idle2" }, + { CharState_Idle3, "idle3" }, + { CharState_Idle4, "idle4" }, + { CharState_Idle5, "idle5" }, + { CharState_Idle6, "idle6" }, + { CharState_Idle7, "idle7" }, + { CharState_Idle8, "idle8" }, + { CharState_Idle9, "idle9" }, + { CharState_IdleSwim, "idleswim" }, + { CharState_IdleSneak, "idlesneak" }, + + { CharState_WalkForward, "walkforward" }, + { CharState_WalkBack, "walkback" }, + { CharState_WalkLeft, "walkleft" }, + { CharState_WalkRight, "walkright" }, + + { CharState_SwimWalkForward, "swimwalkforward" }, + { CharState_SwimWalkBack, "swimwalkback" }, + { CharState_SwimWalkLeft, "swimwalkleft" }, + { CharState_SwimWalkRight, "swimwalkright" }, + + { CharState_RunForward, "runforward" }, + { CharState_RunBack, "runback" }, + { CharState_RunLeft, "runleft" }, + { CharState_RunRight, "runright" }, + + { CharState_SwimRunForward, "swimrunforward" }, + { CharState_SwimRunBack, "swimrunback" }, + { CharState_SwimRunLeft, "swimrunleft" }, + { CharState_SwimRunRight, "swimrunright" }, + + { CharState_SneakForward, "sneakforward" }, + { CharState_SneakBack, "sneakback" }, + { CharState_SneakLeft, "sneakleft" }, + { CharState_SneakRight, "sneakright" }, + + { CharState_Jump, "jump" }, + + { CharState_Death1, "death1" }, + { CharState_Death2, "death2" }, + { CharState_Death3, "death3" }, + { CharState_Death4, "death4" }, + { CharState_Death5, "death5" }, +}; +static const size_t sStateListSize = sizeof(sStateList)/sizeof(sStateList[0]); + +static void getStateInfo(CharacterState state, std::string *group) +{ + for(size_t i = 0;i < sStateListSize;i++) + { + if(sStateList[i].state == state) + { + *group = sStateList[i].groupname; + return; + } + } + throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(state)); +} + + +CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop) + : mPtr(ptr), mAnimation(anim), mState(state), mSkipAnim(false) +{ + if(!mAnimation) + return; + + mAnimation->setController(this); + + getStateInfo(mState, &mCurrentGroup); + if(ptr.getTypeName() == typeid(ESM::Activator).name()) + { + /* Don't accumulate with activators (they don't get moved). */ + mAnimation->setAccumulation(Ogre::Vector3::ZERO); + } + else + { + /* Accumulate along X/Y only for now, until we can figure out how we should + * handle knockout and death which moves the character down. */ + mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f)); + } + if(mAnimation->hasAnimation(mCurrentGroup)) + mAnimation->play(mCurrentGroup, "stop", "stop", loop); +} + +CharacterController::CharacterController(const CharacterController &rhs) + : mPtr(rhs.mPtr), mAnimation(rhs.mAnimation), mAnimQueue(rhs.mAnimQueue) + , mCurrentGroup(rhs.mCurrentGroup), mState(rhs.mState) + , mSkipAnim(rhs.mSkipAnim) +{ + if(!mAnimation) + return; + /* We've been copied. Update the animation with the new controller. */ + mAnimation->setController(this); +} + +CharacterController::~CharacterController() +{ +} + + +void CharacterController::updatePtr(const MWWorld::Ptr &ptr) +{ + mPtr = ptr; +} + + +void CharacterController::markerEvent(float time, const std::string &evt) +{ + if(evt == "stop") + { + if(mAnimQueue.size() >= 2 && mAnimQueue[0] == mAnimQueue[1]) + { + mAnimQueue.pop_front(); + mAnimation->play(mCurrentGroup, "loop start", "stop", false); + } + else if(mAnimQueue.size() > 0) + { + mAnimQueue.pop_front(); + if(mAnimQueue.size() > 0) + { + mCurrentGroup = mAnimQueue.front(); + mAnimation->play(mCurrentGroup, "start", "stop", false); + } + } + return; + } + + std::cerr<< "Unhandled animation event: "<= CharState_Death1)) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + const Ogre::Vector3 &vec = cls.getMovementVector(mPtr); + + bool onground = world->isOnGround(mPtr); + bool inwater = world->isSwimming(mPtr); + bool isrunning = cls.getStance(mPtr, MWWorld::Class::Run); + bool sneak = cls.getStance(mPtr, MWWorld::Class::Sneak); + speed = cls.getSpeed(mPtr); + + /* FIXME: The state should be set to Jump, and X/Y movement should be disallowed except + * for the initial thrust (which would be carried by "physics" until landing). */ + if(onground && vec.z > 0.0f) + { + float x = cls.getJump(mPtr); + + if(vec.x == 0 && vec.y == 0) + movement.z += x*duration; + else + { + /* FIXME: this would be more correct if we were going into a jumping state, + * rather than normal walking/idle states. */ + //Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + //movement += Ogre::Vector3(lat.x, lat.y, 1.0f) * x * 0.707f * duration; + movement.z += x * 0.707f * duration; + } + + //decrease fatigue by fFatigueJumpBase + (1 - normalizedEncumbrance) * fFatigueJumpMult; + } + + if(std::abs(vec.x/2.0f) > std::abs(vec.y) && speed > 0.0f) + { + if(vec.x > 0.0f) + setState(inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight : (isrunning ? CharState_RunRight : CharState_WalkRight)), true); + + else if(vec.x < 0.0f) + setState(inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft)), true); + + // Apply any forward/backward movement manually + movement.y += vec.y * (speed*duration); + } + else if(vec.y != 0.0f && speed > 0.0f) + { + if(vec.y > 0.0f) + setState(inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward)), true); + + else if(vec.y < 0.0f) + setState(inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack)), true); + // Apply any sideways movement manually + movement.x += vec.x * (speed*duration); + } + else if(mAnimQueue.size() == 0) + setState((inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle)), true); + } + + if(mAnimation && !mSkipAnim) + { + mAnimation->setSpeed(speed); + movement += mAnimation->runAnimation(duration); + } + mSkipAnim = false; + + return movement; +} + + +void CharacterController::playGroup(const std::string &groupname, int mode, int count) +{ + if(!mAnimation || !mAnimation->hasAnimation(groupname)) + std::cerr<< "Animation "< 0) + mAnimQueue.push_back(groupname); + mCurrentGroup = groupname; + mState = CharState_SpecialIdle; + mAnimation->play(mCurrentGroup, ((mode==2) ? "loop start" : "start"), "stop", false); + } + else if(mode == 0) + { + mAnimQueue.resize(1); + while(count-- > 0) + mAnimQueue.push_back(groupname); + } + } +} + +void CharacterController::skipAnim() +{ + mSkipAnim = true; +} + + +void CharacterController::setState(CharacterState state, bool loop) +{ + if(mState == state) + { + if(mAnimation) + mAnimation->setLooping(loop); + return; + } + mState = state; + + if(!mAnimation) + return; + mAnimQueue.clear(); + + std::string anim; + getStateInfo(mState, &anim); + if(mAnimation->hasAnimation(anim)) + { + mCurrentGroup = anim; + mAnimation->play(mCurrentGroup, "start", "stop", loop); + } +} + +} diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp new file mode 100644 index 000000000..46f0690e7 --- /dev/null +++ b/apps/openmw/mwmechanics/character.hpp @@ -0,0 +1,102 @@ +#ifndef GAME_MWMECHANICS_CHARACTER_HPP +#define GAME_MWMECHANICS_CHARACTER_HPP + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + class Animation; +} + +namespace MWMechanics +{ + +enum CharacterState { + CharState_SpecialIdle, + CharState_Idle, + CharState_Idle2, + CharState_Idle3, + CharState_Idle4, + CharState_Idle5, + CharState_Idle6, + CharState_Idle7, + CharState_Idle8, + CharState_Idle9, + CharState_IdleSwim, + CharState_IdleSneak, + + CharState_WalkForward, + CharState_WalkBack, + CharState_WalkLeft, + CharState_WalkRight, + + CharState_SwimWalkForward, + CharState_SwimWalkBack, + CharState_SwimWalkLeft, + CharState_SwimWalkRight, + + CharState_RunForward, + CharState_RunBack, + CharState_RunLeft, + CharState_RunRight, + + CharState_SwimRunForward, + CharState_SwimRunBack, + CharState_SwimRunLeft, + CharState_SwimRunRight, + + CharState_SneakForward, + CharState_SneakBack, + CharState_SneakLeft, + CharState_SneakRight, + + CharState_Jump, + + /* Death states must be last! */ + CharState_Death1, + CharState_Death2, + CharState_Death3, + CharState_Death4, + CharState_Death5 +}; + +class CharacterController +{ + MWWorld::Ptr mPtr; + MWRender::Animation *mAnimation; + + typedef std::deque AnimationQueue; + AnimationQueue mAnimQueue; + + std::string mCurrentGroup; + CharacterState mState; + bool mSkipAnim; + +protected: + /* Called by the animation whenever a new text key is reached. */ + void markerEvent(float time, const std::string &evt); + + friend class MWRender::Animation; + +public: + CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop); + CharacterController(const CharacterController &rhs); + virtual ~CharacterController(); + + void updatePtr(const MWWorld::Ptr &ptr); + + Ogre::Vector3 update(float duration); + + void playGroup(const std::string &groupname, int mode, int count); + void skipAnim(); + + void setState(CharacterState state, bool loop); + CharacterState getState() const + { return mState; } +}; + +} + +#endif /* GAME_MWMECHANICS_CHARACTER_HPP */ diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 94363cb79..1a7b34817 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -68,7 +68,7 @@ namespace MWMechanics } } - void MagicEffects::add (const ESM::EffectList& list) + void MagicEffects::add (const ESM::EffectList& list, float magnitude) { for (std::vector::const_iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) @@ -78,9 +78,13 @@ namespace MWMechanics if (iter->mMagnMin>=iter->mMagnMax) param.mMagnitude = iter->mMagnMin; else + { + if (magnitude==-1) + magnitude = static_cast (std::rand()) / RAND_MAX; + param.mMagnitude = static_cast ( - (iter->mMagnMax-iter->mMagnMin+1)* - (static_cast (std::rand()) / RAND_MAX) + iter->mMagnMin); + (iter->mMagnMax-iter->mMagnMin+1)*magnitude + iter->mMagnMin); + } add (*iter, param); } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 2f61d7eeb..b80b5a863 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -67,7 +67,8 @@ namespace MWMechanics void add (const EffectKey& key, const EffectParam& param); - void add (const ESM::EffectList& list); + void add (const ESM::EffectList& list, float magnitude = -1); + ///< \param magnitude normalised magnitude (-1: random) MagicEffects& operator+= (const MagicEffects& effects); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 079f8520b..c9e4c77ea 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -175,34 +175,47 @@ namespace MWMechanics buildPlayer(); } - void MechanicsManager::addActor (const MWWorld::Ptr& ptr) + void MechanicsManager::add(const MWWorld::Ptr& ptr) { - mActors.addActor (ptr); + if(ptr.getTypeName() == typeid(ESM::Activator).name()) + mActivators.addActivator(ptr); + else + mActors.addActor(ptr); } - void MechanicsManager::removeActor (const MWWorld::Ptr& ptr) + void MechanicsManager::remove(const MWWorld::Ptr& ptr) { - if (ptr==mWatched) + if(ptr == mWatched) + mWatched = MWWorld::Ptr(); + mActors.removeActor(ptr); + mActivators.removeActivator(ptr); + } + + void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) + { + if(ptr.getTypeName() == typeid(ESM::Activator).name()) + mActivators.updateActivator(old, ptr); + else + mActors.updateActor(old, ptr); + } + + + void MechanicsManager::drop(const MWWorld::CellStore *cellStore) + { + if(!mWatched.isEmpty() && mWatched.getCell() == cellStore) mWatched = MWWorld::Ptr(); - mActors.removeActor (ptr); + mActors.dropActors(cellStore); + mActivators.dropActivators(cellStore); } - void MechanicsManager::dropActors (const MWWorld::Ptr::CellStore *cellStore) - { - if (!mWatched.isEmpty() && mWatched.getCell()==cellStore) - mWatched = MWWorld::Ptr(); - mActors.dropActors (cellStore); - } - - void MechanicsManager::watchActor (const MWWorld::Ptr& ptr) + void MechanicsManager::watchActor(const MWWorld::Ptr& ptr) { mWatched = ptr; } - void MechanicsManager::update (std::vector >& movement, - float duration, bool paused) + void MechanicsManager::update(float duration, bool paused) { if (!mWatched.isEmpty()) { @@ -296,9 +309,16 @@ namespace MWMechanics } winMgr->configureSkills (majorSkills, minorSkills); + + // HACK? The player has been changed, so a new Animation object may + // have been made for them. Make sure they're properly updated. + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + mActors.removeActor(ptr); + mActors.addActor(ptr); } - mActors.update (movement, duration, paused); + mActors.update(duration, paused); + mActivators.update(duration, paused); } void MechanicsManager::restoreDynamicStats() @@ -377,16 +397,6 @@ namespace MWMechanics mUpdatePlayer = true; } - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr) { MWMechanics::NpcStats npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr); @@ -398,7 +408,7 @@ namespace MWMechanics MWMechanics::CreatureStats playerStats = MWWorld::Class::get(playerPtr).getCreatureStats(playerPtr); MWMechanics::NpcStats playerNpcStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - if (toLower(npc->mBase->mRace) == toLower(player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); + if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispPersonalityMult")->getFloat() * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - MWBase::Environment::get().getWorld()->getStore().get().find("fDispPersonalityBase")->getFloat()); @@ -408,21 +418,21 @@ namespace MWMechanics std::string npcFaction = ""; if(!npcSkill.getFactionRanks().empty()) npcFaction = npcSkill.getFactionRanks().begin()->first; - if (playerNpcStats.getFactionRanks().find(toLower(npcFaction)) != playerNpcStats.getFactionRanks().end()) + if (playerNpcStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction)) != playerNpcStats.getFactionRanks().end()) { - for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(toLower(npcFaction))->mReactions.begin(); - it != MWBase::Environment::get().getWorld()->getStore().get().find(toLower(npcFaction))->mReactions.end(); it++) + for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin(); + it != MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end(); it++) { - if(toLower(it->mFaction) == toLower(npcFaction)) reaction = it->mReaction; + if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction; } - rank = playerNpcStats.getFactionRanks().find(toLower(npcFaction))->second; + rank = playerNpcStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second; } else if (npcFaction != "") { - for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(toLower(npcFaction))->mReactions.begin(); - it != MWBase::Environment::get().getWorld()->getStore().get().find(toLower(npcFaction))->mReactions.end();it++) + for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin(); + it != MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end();it++) { - if(playerNpcStats.getFactionRanks().find(toLower(it->mFaction)) != playerNpcStats.getFactionRanks().end() ) + if(playerNpcStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerNpcStats.getFactionRanks().end() ) { if(it->mReactionmReaction; } @@ -481,8 +491,10 @@ namespace MWMechanics if(buying) x = buyTerm; else x = std::min(buyTerm, sellTerm); int offerPrice; - if (x < 1) offerPrice = int(x * basePrice); - if (x >= 1) offerPrice = basePrice + int((x - 1) * basePrice); + if (x < 1) + offerPrice = int(x * basePrice); + else + offerPrice = basePrice + int((x - 1) * basePrice); offerPrice = std::max(1, offerPrice); return offerPrice; } @@ -545,7 +557,8 @@ namespace MWMechanics float fPerDieRollMult = gmst.find("fPerDieRollMult")->getFloat(); float fPerTempMult = gmst.find("fPerTempMult")->getFloat(); - float x,y; + float x = 0; + float y = 0; float roll = static_cast (std::rand()) / RAND_MAX * 100; @@ -639,4 +652,20 @@ namespace MWMechanics permChange = success ? -int(cappedDispositionChange/ fPerTempMult) : y; } } + + void MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + { + if(ptr.getTypeName() == typeid(ESM::Activator).name()) + mActivators.playAnimationGroup(ptr, groupName, mode, number); + else + mActors.playAnimationGroup(ptr, groupName, mode, number); + } + void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) + { + if(ptr.getTypeName() == typeid(ESM::Activator).name()) + mActivators.skipAnimation(ptr); + else + mActors.skipAnimation(ptr); + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index f8d470a4e..5ad870571 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -7,6 +7,7 @@ #include "creaturestats.hpp" #include "npcstats.hpp" +#include "activators.hpp" #include "actors.hpp" namespace Ogre @@ -29,6 +30,8 @@ namespace MWMechanics bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; + + Activators mActivators; Actors mActors; void buildPlayer(); @@ -39,24 +42,24 @@ namespace MWMechanics MechanicsManager(); - virtual void addActor (const MWWorld::Ptr& ptr); - ///< Register an actor for stats management - /// - /// \note Dead actors are ignored. + virtual void add (const MWWorld::Ptr& ptr); + ///< Register an object for management - virtual void removeActor (const MWWorld::Ptr& ptr); - ///< Deregister an actor for stats management + virtual void remove (const MWWorld::Ptr& ptr); + ///< Deregister an object for management - virtual void dropActors (const MWWorld::CellStore *cellStore); - ///< Deregister all actors in the given cell. + virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr); + ///< Moves an object to a new cell - virtual void watchActor (const MWWorld::Ptr& ptr); + virtual void drop(const MWWorld::CellStore *cellStore); + ///< Deregister all objects in the given cell. + + virtual void watchActor(const MWWorld::Ptr& ptr); ///< On each update look for changes in a previously registered actor and update the /// GUI accordingly. - virtual void update (std::vector >& movement, - float duration, bool paused); - ///< Update actor stats and store desired velocity vectors in \a movement + virtual void update (float duration, bool paused); + ///< Update objects /// /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). @@ -90,7 +93,11 @@ namespace MWMechanics virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange); + void toLower(std::string npcFaction); ///< Perform a persuasion action on NPC + + virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + virtual void skipAnimation(const MWWorld::Ptr& ptr); }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index ef084f479..e2da7cdc8 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,22 +1,19 @@ #include "spells.hpp" -#include "../mwworld/esmstore.hpp" +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + #include "magiceffects.hpp" namespace MWMechanics { - void Spells::addSpell (const ESM::Spell *spell, MagicEffects& effects) const - { - effects.add (spell->mEffects); - } - Spells::TIterator Spells::begin() const { return mSpells.begin(); @@ -29,13 +26,13 @@ namespace MWMechanics void Spells::add (const std::string& spellId) { - if (std::find (mSpells.begin(), mSpells.end(), spellId)==mSpells.end()) - mSpells.push_back (spellId); + if (mSpells.find (spellId)==mSpells.end()) + mSpells.insert (std::make_pair (spellId, static_cast (std::rand()) / RAND_MAX)); } void Spells::remove (const std::string& spellId) { - TContainer::iterator iter = std::find (mSpells.begin(), mSpells.end(), spellId); + TContainer::iterator iter = mSpells.find (spellId); if (iter!=mSpells.end()) mSpells.erase (iter); @@ -51,11 +48,11 @@ namespace MWMechanics for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (*iter); + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - addSpell (spell, effects); + effects.add (spell->mEffects, iter->second); } return effects; @@ -75,18 +72,18 @@ namespace MWMechanics { return mSelectedSpell; } - + bool Spells::hasCommonDisease() const { for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (*iter); - + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + if (spell->mData.mFlags & ESM::Spell::ST_Disease) return true; } - + return false; } @@ -95,12 +92,12 @@ namespace MWMechanics for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (*iter); - + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + if (spell->mData.mFlags & ESM::Spell::ST_Blight) return true; } - - return false; + + return false; } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 12308661b..e00ac259f 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H -#include +#include #include namespace ESM @@ -21,16 +21,14 @@ namespace MWMechanics { public: - typedef std::vector TContainer; + typedef std::map TContainer; // ID, normalised magnitude typedef TContainer::const_iterator TIterator; private: - std::vector mSpells; + TContainer mSpells; std::string mSelectedSpell; - void addSpell (const ESM::Spell *, MagicEffects& effects) const; - public: TIterator begin() const; @@ -55,10 +53,10 @@ namespace MWMechanics const std::string getSelectedSpell() const; ///< May return an empty string. - + bool hasCommonDisease() const; - bool hasBlightDisease() const; + bool hasBlightDisease() const; }; } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index d576020c5..e9b7f4385 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -26,9 +26,9 @@ namespace MWMechanics return mBase; } - const T& getModified() const + T getModified() const { - return mModified; + return std::max(static_cast(0), mModified); } T getModifier() const @@ -108,7 +108,7 @@ namespace MWMechanics return mStatic.getBase(); } - const T& getModified() const + T getModified() const { return mStatic.getModified(); } diff --git a/apps/openmw/mwrender/activatoranimation.cpp b/apps/openmw/mwrender/activatoranimation.cpp new file mode 100644 index 000000000..961c07003 --- /dev/null +++ b/apps/openmw/mwrender/activatoranimation.cpp @@ -0,0 +1,45 @@ +#include "activatoranimation.hpp" + +#include +#include +#include + +#include "renderconst.hpp" + +#include "../mwbase/world.hpp" + +namespace MWRender +{ + +ActivatorAnimation::~ActivatorAnimation() +{ +} + +ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr) + : Animation(ptr) +{ + MWWorld::LiveCellRef *ref = mPtr.get(); + + assert (ref->mBase != NULL); + if(!ref->mBase->mModel.empty()) + { + std::string mesh = "meshes\\" + ref->mBase->mModel; + + createEntityList(mPtr.getRefData().getBaseNode(), mesh); + for(size_t i = 0;i < mEntityList.mEntities.size();i++) + { + Ogre::Entity *ent = mEntityList.mEntities[i]; + + for(unsigned int j=0; j < ent->getNumSubEntities(); ++j) + { + Ogre::SubEntity* subEnt = ent->getSubEntity(j); + subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); + } + + ent->setVisibilityFlags(RV_Misc); + } + setAnimationSource(mesh); + } +} + +} diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp new file mode 100644 index 000000000..f3ea38f44 --- /dev/null +++ b/apps/openmw/mwrender/activatoranimation.hpp @@ -0,0 +1,21 @@ +#ifndef _GAME_RENDER_ACTIVATORANIMATION_H +#define _GAME_RENDER_ACTIVATORANIMATION_H + +#include "animation.hpp" + +namespace MWWorld +{ + class Ptr; +} + +namespace MWRender +{ + class ActivatorAnimation : public Animation + { + public: + ActivatorAnimation(const MWWorld::Ptr& ptr); + virtual ~ActivatorAnimation(); + }; +} + +#endif diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 05bb030d7..644d3613b 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -3,43 +3,47 @@ #include #include +#include "../mwworld/ptr.hpp" +#include "../mwworld/class.hpp" + +#include "../mwrender/renderingmanager.hpp" + +#include "animation.hpp" +#include "activatoranimation.hpp" +#include "creatureanimation.hpp" +#include "npcanimation.hpp" + #include "renderconst.hpp" +namespace MWRender +{ using namespace Ogre; -using namespace MWRender; -using namespace NifOgre; -Actors::~Actors(){ - - std::map::iterator it = mAllActors.begin(); - for (; it != mAllActors.end(); ++it) { +Actors::~Actors() +{ + PtrAnimationMap::iterator it = mAllActors.begin(); + for(;it != mAllActors.end();++it) + { delete it->second; it->second = NULL; } } -void Actors::setMwRoot(Ogre::SceneNode* root){ - mMwRoot = root; -} -void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv){ +void Actors::setRootNode(Ogre::SceneNode* root) +{ mRootNode = root; } - insertBegin(ptr, true, true); - NpcAnimation* anim = new MWRender::NpcAnimation(ptr, ptr.getRefData ().getBaseNode (), inv, RV_Actors); - - mAllActors[ptr] = anim; -} -void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){ +void Actors::insertBegin(const MWWorld::Ptr &ptr) +{ Ogre::SceneNode* cellnode; - if(mCellSceneNodes.find(ptr.getCell()) == mCellSceneNodes.end()) - { - //Create the scenenode and put it in the map - cellnode = mMwRoot->createChildSceneNode(); - mCellSceneNodes[ptr.getCell()] = cellnode; - } + CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(ptr.getCell()); + if(celliter != mCellSceneNodes.end()) + cellnode = celliter->second; else { - cellnode = mCellSceneNodes[ptr.getCell()]; + //Create the scenenode and put it in the map + cellnode = mRootNode->createChildSceneNode(); + mCellSceneNodes[ptr.getCell()] = cellnode; } Ogre::SceneNode* insert = cellnode->createChildSceneNode(); @@ -51,50 +55,64 @@ void Actors::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_){ f = ptr.getCellRef().mPos.rot; // Rotate around X axis - Quaternion xr(Radian(-f[0]), Vector3::UNIT_X); + Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X); // Rotate around Y axis - Quaternion yr(Radian(-f[1]), Vector3::UNIT_Y); + Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y); // Rotate around Z axis - Quaternion zr(Radian(-f[2]), Vector3::UNIT_Z); + Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z); // Rotates first around z, then y, then x insert->setOrientation(xr*yr*zr); - if (!enabled) - insert->setVisible (false); ptr.getRefData().setBaseNode(insert); - - } -void Actors::insertCreature (const MWWorld::Ptr& ptr){ - insertBegin(ptr, true, true); - CreatureAnimation* anim = new MWRender::CreatureAnimation(ptr); - //mAllActors.insert(std::pair(ptr,anim)); +void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv) +{ + insertBegin(ptr); + NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), inv, RV_Actors); + delete mAllActors[ptr]; + mAllActors[ptr] = anim; + mRendering->addWaterRippleEmitter (ptr); +} +void Actors::insertCreature (const MWWorld::Ptr& ptr) +{ + insertBegin(ptr); + CreatureAnimation* anim = new CreatureAnimation(ptr); + delete mAllActors[ptr]; + mAllActors[ptr] = anim; + mRendering->addWaterRippleEmitter (ptr); +} +void Actors::insertActivator (const MWWorld::Ptr& ptr) +{ + insertBegin(ptr); + ActivatorAnimation* anim = new ActivatorAnimation(ptr); delete mAllActors[ptr]; mAllActors[ptr] = anim; - //mAllActors.push_back(&anim);*/ } bool Actors::deleteObject (const MWWorld::Ptr& ptr) { - delete mAllActors[ptr]; - mAllActors.erase(ptr); - if (Ogre::SceneNode *base = ptr.getRefData().getBaseNode()) + mRendering->removeWaterRippleEmitter (ptr); + + delete mAllActors[ptr]; + mAllActors.erase(ptr); + + if(Ogre::SceneNode *base=ptr.getRefData().getBaseNode()) { - Ogre::SceneNode *parent = base->getParentSceneNode(); - - for (std::map::const_iterator iter ( - mCellSceneNodes.begin()); iter!=mCellSceneNodes.end(); ++iter) - if (iter->second==parent) + CellSceneNodeMap::const_iterator iter(mCellSceneNodes.begin()); + for(;iter != mCellSceneNodes.end();++iter) + { + if(iter->second == parent) { base->removeAndDestroyAllChildren(); mRend.getScene()->destroySceneNode (base); ptr.getRefData().setBaseNode (0); return true; } + } return false; } @@ -102,57 +120,67 @@ bool Actors::deleteObject (const MWWorld::Ptr& ptr) return true; } -void Actors::removeCell(MWWorld::Ptr::CellStore* store){ - if(mCellSceneNodes.find(store) != mCellSceneNodes.end()) +void Actors::removeCell(MWWorld::Ptr::CellStore* store) +{ + for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();) { - Ogre::SceneNode* base = mCellSceneNodes[store]; - base->removeAndDestroyAllChildren(); - mCellSceneNodes.erase(store); - mRend.getScene()->destroySceneNode(base); - base = 0; - } - for(std::map::iterator iter = mAllActors.begin(); iter != mAllActors.end(); ) - { - if(iter->first.getCell() == store){ + if(iter->first.getCell() == store) + { + mRendering->removeWaterRippleEmitter (iter->first); delete iter->second; mAllActors.erase(iter++); } else ++iter; } + CellSceneNodeMap::iterator celliter = mCellSceneNodes.find(store); + if(celliter != mCellSceneNodes.end()) + { + Ogre::SceneNode *base = celliter->second; + base->removeAndDestroyAllChildren(); + mRend.getScene()->destroySceneNode(base); + mCellSceneNodes.erase(celliter); + } } -void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number){ - if(mAllActors.find(ptr) != mAllActors.end()) - mAllActors[ptr]->playGroup(groupName, mode, number); -} -void Actors::skipAnimation (const MWWorld::Ptr& ptr){ - if(mAllActors.find(ptr) != mAllActors.end()) - mAllActors[ptr]->skipAnim(); -} -void Actors::update (float duration){ - for(std::map::iterator iter = mAllActors.begin(); iter != mAllActors.end(); iter++) - iter->second->runAnimation(duration); +void Actors::update (float duration) +{ + // Nothing to do } -void -Actors::updateObjectCell(const MWWorld::Ptr &ptr) +Animation* Actors::getAnimation(const MWWorld::Ptr &ptr) +{ + PtrAnimationMap::const_iterator iter = mAllActors.find(ptr); + if(iter != mAllActors.end()) + return iter->second; + return NULL; +} + +void Actors::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { Ogre::SceneNode *node; - MWWorld::CellStore *newCell = ptr.getCell(); + MWWorld::CellStore *newCell = cur.getCell(); - if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) { - node = mMwRoot->createChildSceneNode(); + CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(newCell); + if(celliter != mCellSceneNodes.end()) + node = celliter->second; + else + { + node = mRootNode->createChildSceneNode(); mCellSceneNodes[newCell] = node; - } else { - node = mCellSceneNodes[newCell]; } - node->addChild(ptr.getRefData().getBaseNode()); - if (mAllActors.find(ptr) != mAllActors.end()) { - /// \note Update key (Ptr's are compared only with refdata so mCell - /// on key is outdated), maybe redundant - Animation *anim = mAllActors[ptr]; - mAllActors.erase(ptr); - mAllActors[ptr] = anim; + node->addChild(cur.getRefData().getBaseNode()); + + PtrAnimationMap::iterator iter = mAllActors.find(old); + if(iter != mAllActors.end()) + { + Animation *anim = iter->second; + mAllActors.erase(iter); + anim->updatePtr(cur); + mAllActors[cur] = anim; } + + mRendering->updateWaterRippleEmitterPtr (old, cur); +} + } diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 073c5d51f..bba2d945c 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -1,52 +1,55 @@ #ifndef _GAME_RENDER_ACTORS_H #define _GAME_RENDER_ACTORS_H -#include "npcanimation.hpp" -#include "creatureanimation.hpp" +#include namespace MWWorld { class Ptr; class CellStore; + class InventoryStore; } -namespace MWRender{ - class Actors{ +namespace MWRender +{ + class Animation; + class RenderingManager; + + class Actors + { + typedef std::map CellSceneNodeMap; + typedef std::map PtrAnimationMap; + OEngine::Render::OgreRenderer &mRend; - std::map mCellSceneNodes; - Ogre::SceneNode* mMwRoot; - std::map mAllActors; + MWRender::RenderingManager* mRendering; + Ogre::SceneNode* mRootNode; + CellSceneNodeMap mCellSceneNodes; + PtrAnimationMap mAllActors; - - public: - Actors(OEngine::Render::OgreRenderer& _rend): mRend(_rend) {} + public: + Actors(OEngine::Render::OgreRenderer& _rend, MWRender::RenderingManager* rendering) + : mRend(_rend) + , mRendering(rendering) + {} ~Actors(); - void setMwRoot(Ogre::SceneNode* root); - void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_); - void insertCreature (const MWWorld::Ptr& ptr); + + void setRootNode(Ogre::SceneNode* root); + void insertBegin (const MWWorld::Ptr& ptr); void insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv); + void insertCreature (const MWWorld::Ptr& ptr); + void insertActivator (const MWWorld::Ptr& ptr); bool deleteObject (const MWWorld::Ptr& ptr); ///< \return found? void removeCell(MWWorld::CellStore* store); - void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, - int number = 1); - ///< Run animation for a MW-reference. Calls to this function for references that are currently not - /// in the rendered scene should be ignored. - /// - /// \param mode: 0 normal, 1 immediate start, 2 immediate loop - /// \param number How offen the animation should be run - - void skipAnimation (const MWWorld::Ptr& ptr); - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the rendered scene should be ignored. - void update (float duration); /// Updates containing cell for object rendering data - void updateObjectCell(const MWWorld::Ptr &ptr); + void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); + + Animation* getAnimation(const MWWorld::Ptr &ptr); }; } #endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 2a3b8cf43..cc926e685 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1,148 +1,453 @@ #include "animation.hpp" -#include +#include #include #include #include #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/character.hpp" + namespace MWRender { -Animation::Animation() - : mInsert(NULL) - , mTime(0.0f) - , mSkipFrame(false) +Animation::Animation(const MWWorld::Ptr &ptr) + : mPtr(ptr) + , mController(NULL) + , mInsert(NULL) + , mAccumRoot(NULL) + , mNonAccumRoot(NULL) + , mAccumulate(Ogre::Vector3::ZERO) + , mLastPosition(0.0f) + , mCurrentKeys(NULL) + , mCurrentAnim(NULL) + , mCurrentTime(0.0f) + , mStopTime(0.0f) + , mPlaying(false) + , mLooping(false) + , mAnimVelocity(0.0f) + , mAnimSpeedMult(1.0f) { } Animation::~Animation() { - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - for(size_t i = 0;i < mEntityList.mEntities.size();i++) - sceneMgr->destroyEntity(mEntityList.mEntities[i]); + if(mInsert) + { + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); + for(size_t i = 0;i < mEntityList.mEntities.size();i++) + sceneMgr->destroyEntity(mEntityList.mEntities[i]); + } mEntityList.mEntities.clear(); + mEntityList.mSkelBase = NULL; } -struct checklow { - bool operator()(const char &a, const char &b) const - { - return ::tolower(a) == ::tolower(b); - } -}; - -bool Animation::findGroupTimes(const std::string &groupname, Animation::GroupTimes *times) +void Animation::setAnimationSources(const std::vector &names) { - const std::string &start = groupname+": start"; - const std::string &startloop = groupname+": loop start"; - const std::string &stop = groupname+": stop"; - const std::string &stoploop = groupname+": loop stop"; + if(!mEntityList.mSkelBase) + return; - NifOgre::TextKeyMap::const_iterator iter; - for(iter = mTextKeys.begin();iter != mTextKeys.end();iter++) + mCurrentAnim = NULL; + mCurrentKeys = NULL; + mAnimVelocity = 0.0f; + mAccumRoot = NULL; + mNonAccumRoot = NULL; + mSkeletonSources.clear(); + + std::vector::const_iterator nameiter; + for(nameiter = names.begin();nameiter != names.end();nameiter++) { - if(times->mStart >= 0.0f && times->mLoopStart >= 0.0f && times->mLoopStop >= 0.0f && times->mStop >= 0.0f) - return true; + Ogre::SkeletonPtr skel = NifOgre::Loader::getSkeleton(*nameiter); + if(skel.isNull()) + { + std::cerr<< "Failed to get skeleton source "<<*nameiter <touch(); - std::string::const_iterator strpos = iter->second.begin(); - std::string::const_iterator strend = iter->second.end(); - size_t strlen = strend-strpos; + Ogre::Skeleton::BoneIterator boneiter = skel->getBoneIterator(); + while(boneiter.hasMoreElements()) + { + Ogre::Bone *bone = boneiter.getNext(); + Ogre::UserObjectBindings &bindings = bone->getUserObjectBindings(); + const Ogre::Any &data = bindings.getUserAny(NifOgre::sTextKeyExtraDataID); + if(data.isEmpty() || !Ogre::any_cast(data)) + continue; + + if(!mNonAccumRoot) + { + mAccumRoot = mInsert; + mNonAccumRoot = mEntityList.mSkelBase->getSkeleton()->getBone(bone->getName()); + } + + mSkeletonSources.push_back(skel); + for(int i = 0;i < skel->getNumAnimations();i++) + { + Ogre::Animation *anim = skel->getAnimation(i); + const Ogre::Any &groupdata = bindings.getUserAny(std::string(NifOgre::sTextKeyExtraDataID)+ + "@"+anim->getName()); + if(!groupdata.isEmpty()) + mTextKeys[anim->getName()] = Ogre::any_cast(groupdata); + } - if(start.size() <= strlen && std::mismatch(strpos, strend, start.begin(), checklow()).first == strend) - { - times->mStart = iter->first; - times->mLoopStart = iter->first; - } - else if(startloop.size() <= strlen && std::mismatch(strpos, strend, startloop.begin(), checklow()).first == strend) - { - times->mLoopStart = iter->first; - } - else if(stoploop.size() <= strlen && std::mismatch(strpos, strend, stoploop.begin(), checklow()).first == strend) - { - times->mLoopStop = iter->first; - } - else if(stop.size() <= strlen && std::mismatch(strpos, strend, stop.begin(), checklow()).first == strend) - { - times->mStop = iter->first; - if(times->mLoopStop < 0.0f) - times->mLoopStop = iter->first; break; } } +} - return (times->mStart >= 0.0f && times->mLoopStart >= 0.0f && times->mLoopStop >= 0.0f && times->mStop >= 0.0f); +void Animation::createEntityList(Ogre::SceneNode *node, const std::string &model) +{ + mInsert = node->createChildSceneNode(); + assert(mInsert); + + mEntityList = NifOgre::Loader::createEntities(mInsert, model); + if(mEntityList.mSkelBase) + { + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); + while(asiter.hasMoreElements()) + { + Ogre::AnimationState *state = asiter.getNext(); + state->setEnabled(false); + state->setLoop(false); + } + + // Set the bones as manually controlled since we're applying the + // transformations manually (needed if we want to apply an animation + // from one skeleton onto another). + Ogre::SkeletonInstance *skelinst = mEntityList.mSkelBase->getSkeleton(); + Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); + while(boneiter.hasMoreElements()) + boneiter.getNext()->setManuallyControlled(true); + } } -void Animation::playGroup(std::string groupname, int mode, int loops) +bool Animation::hasAnimation(const std::string &anim) { - GroupTimes times; - times.mLoops = loops; - - if(groupname == "all") + for(std::vector::const_iterator iter(mSkeletonSources.begin());iter != mSkeletonSources.end();iter++) { - times.mStart = times.mLoopStart = 0.0f; - times.mLoopStop = times.mStop = 0.0f; - - NifOgre::TextKeyMap::const_reverse_iterator iter = mTextKeys.rbegin(); - if(iter != mTextKeys.rend()) - times.mLoopStop = times.mStop = iter->first; + if((*iter)->hasAnimation(anim)) + return true; } - else if(!findGroupTimes(groupname, ×)) - throw std::runtime_error("Failed to find animation group "+groupname); + return false; +} - if(mode == 0 && mCurGroup.mLoops > 0) - mNextGroup = times; + +void Animation::setController(MWMechanics::CharacterController *controller) +{ + mController = controller; +} + + +void Animation::setAccumulation(const Ogre::Vector3 &accum) +{ + mAccumulate = accum; +} + +void Animation::setSpeed(float speed) +{ + mAnimSpeedMult = 1.0f; + if(mAnimVelocity > 1.0f && speed > 0.0f) + mAnimSpeedMult = speed / mAnimVelocity; +} + +void Animation::setLooping(bool loop) +{ + mLooping = loop; +} + +void Animation::updatePtr(const MWWorld::Ptr &ptr) +{ + mPtr = ptr; +} + + +void Animation::calcAnimVelocity() +{ + const Ogre::NodeAnimationTrack *track = 0; + + Ogre::Animation::NodeTrackIterator trackiter = mCurrentAnim->getNodeTrackIterator(); + while(!track && trackiter.hasMoreElements()) + { + const Ogre::NodeAnimationTrack *cur = trackiter.getNext(); + if(cur->getAssociatedNode()->getName() == mNonAccumRoot->getName()) + track = cur; + } + + if(track && track->getNumKeyFrames() > 1) + { + float loopstarttime = 0.0f; + float loopstoptime = mCurrentAnim->getLength(); + NifOgre::TextKeyMap::const_iterator keyiter = mCurrentKeys->begin(); + while(keyiter != mCurrentKeys->end()) + { + if(keyiter->second == "loop start") + loopstarttime = keyiter->first; + else if(keyiter->second == "loop stop") + { + loopstoptime = keyiter->first; + break; + } + keyiter++; + } + + if(loopstoptime > loopstarttime) + { + Ogre::TransformKeyFrame startkf(0, loopstarttime); + Ogre::TransformKeyFrame endkf(0, loopstoptime); + + track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(loopstarttime), &startkf); + track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(loopstoptime), &endkf); + + mAnimVelocity = startkf.getTranslate().distance(endkf.getTranslate()) / + (loopstoptime-loopstarttime); + } + } +} + +void Animation::applyAnimation(const Ogre::Animation *anim, float time, Ogre::SkeletonInstance *skel) +{ + Ogre::TimeIndex timeindex = anim->_getTimeIndex(time); + Ogre::Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator(); + while(tracks.hasMoreElements()) + { + Ogre::NodeAnimationTrack *track = tracks.getNext(); + const Ogre::String &targetname = track->getAssociatedNode()->getName(); + if(!skel->hasBone(targetname)) + continue; + Ogre::Bone *bone = skel->getBone(targetname); + bone->setOrientation(Ogre::Quaternion::IDENTITY); + bone->setPosition(Ogre::Vector3::ZERO); + bone->setScale(Ogre::Vector3::UNIT_SCALE); + track->applyToNode(bone, timeindex); + } + + // HACK: Dirty the animation state set so that Ogre will apply the + // transformations to entities this skeleton instance is shared with. + mEntityList.mSkelBase->getAllAnimationStates()->_notifyDirty(); +} + +static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone) +{ + if(skelsrc->hasBone(bone->getName())) + { + Ogre::Bone *srcbone = skelsrc->getBone(bone->getName()); + if(!srcbone->getParent() || !bone->getParent()) + { + bone->setOrientation(srcbone->getOrientation()); + bone->setPosition(srcbone->getPosition()); + bone->setScale(srcbone->getScale()); + } + else + { + bone->_setDerivedOrientation(srcbone->_getDerivedOrientation()); + bone->_setDerivedPosition(srcbone->_getDerivedPosition()); + bone->setScale(Ogre::Vector3::UNIT_SCALE); + } + } else { - mCurGroup = times; - mNextGroup = GroupTimes(); - mTime = ((mode==2) ? mCurGroup.mLoopStart : mCurGroup.mStart); + // No matching bone in the source. Make sure it stays properly offset + // from its parent. + bone->resetToInitialState(); } + + Ogre::Node::ChildNodeIterator boneiter = bone->getChildIterator(); + while(boneiter.hasMoreElements()) + updateBoneTree(skelsrc, static_cast(boneiter.getNext())); } -void Animation::skipAnim() +void Animation::updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel) { - mSkipFrame = true; + Ogre::Skeleton::BoneIterator boneiter = skel->getRootBoneIterator(); + while(boneiter.hasMoreElements()) + updateBoneTree(skelsrc, boneiter.getNext()); } -void Animation::runAnimation(float timepassed) + +Ogre::Vector3 Animation::updatePosition(float time) { - if(mCurGroup.mLoops > 0 && !mSkipFrame) + if(mLooping) + mCurrentTime = std::fmod(std::max(time, 0.0f), mCurrentAnim->getLength()); + else + mCurrentTime = std::min(mCurrentAnim->getLength(), std::max(time, 0.0f)); + applyAnimation(mCurrentAnim, mCurrentTime, mEntityList.mSkelBase->getSkeleton()); + + Ogre::Vector3 posdiff = Ogre::Vector3::ZERO; + if(mNonAccumRoot) { - mTime += timepassed; - if(mTime >= mCurGroup.mLoopStop) + /* Get the non-accumulation root's difference from the last update. */ + posdiff = (mNonAccumRoot->getPosition() - mLastPosition) * mAccumulate; + + /* Translate the accumulation root back to compensate for the move. */ + mLastPosition += posdiff; + mAccumRoot->setPosition(-mLastPosition); + } + return posdiff; +} + +void Animation::reset(const std::string &start, const std::string &stop) +{ + mNextKey = mCurrentKeys->begin(); + + while(mNextKey != mCurrentKeys->end() && mNextKey->second != start) + mNextKey++; + if(mNextKey != mCurrentKeys->end()) + mCurrentTime = mNextKey->first; + else + { + mNextKey = mCurrentKeys->begin(); + mCurrentTime = 0.0f; + } + + if(stop.length() > 0) + { + NifOgre::TextKeyMap::const_iterator stopKey = mNextKey; + while(stopKey != mCurrentKeys->end() && stopKey->second != stop) + stopKey++; + if(stopKey != mCurrentKeys->end()) + mStopTime = stopKey->first; + else + mStopTime = mCurrentAnim->getLength(); + } + + if(mNonAccumRoot) + { + const Ogre::NodeAnimationTrack *track = 0; + Ogre::Animation::NodeTrackIterator trackiter = mCurrentAnim->getNodeTrackIterator(); + while(!track && trackiter.hasMoreElements()) { - if(mCurGroup.mLoops > 1) - { - mCurGroup.mLoops--; - mTime = mTime - mCurGroup.mLoopStop + mCurGroup.mLoopStart; - } - else if(mTime >= mCurGroup.mStop) - { - if(mNextGroup.mLoops > 0) - mTime = mTime - mCurGroup.mStop + mNextGroup.mStart; - else - mTime = mCurGroup.mStop; - mCurGroup = mNextGroup; - mNextGroup = GroupTimes(); - } + const Ogre::NodeAnimationTrack *cur = trackiter.getNext(); + if(cur->getAssociatedNode()->getName() == mNonAccumRoot->getName()) + track = cur; } - if(mEntityList.mSkelBase) + if(track) { - Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); - while(as.hasMoreElements()) - { - Ogre::AnimationState *state = as.getNext(); - state->setTimePosition(mTime); - } + Ogre::TransformKeyFrame kf(0, mCurrentTime); + track->getInterpolatedKeyFrame(mCurrentAnim->_getTimeIndex(mCurrentTime), &kf); + mLastPosition = kf.getTranslate() * mAccumulate; } } - mSkipFrame = false; +} + + +bool Animation::handleEvent(float time, const std::string &evt) +{ + if(evt == "start" || evt == "loop start") + { + /* Do nothing */ + return true; + } + + if(evt.compare(0, 7, "sound: ") == 0) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); + return true; + } + if(evt.compare(0, 10, "soundgen: ") == 0) + { + // FIXME: Lookup the SoundGen (SNDG) for the specified sound that corresponds + // to this actor type + return true; + } + + if(evt == "loop stop") + { + if(mLooping) + { + reset("loop start", ""); + if(mCurrentTime >= time) + return false; + } + return true; + } + if(evt == "stop") + { + if(mLooping) + { + reset("loop start", ""); + if(mCurrentTime >= time) + return false; + return true; + } + // fall-through + } + if(mController) + mController->markerEvent(time, evt); + return true; +} + + +void Animation::play(const std::string &groupname, const std::string &start, const std::string &stop, bool loop) +{ + try { + bool found = false; + /* Look in reverse; last-inserted source has priority. */ + for(std::vector::const_reverse_iterator iter(mSkeletonSources.rbegin());iter != mSkeletonSources.rend();iter++) + { + if((*iter)->hasAnimation(groupname)) + { + mCurrentAnim = (*iter)->getAnimation(groupname); + mCurrentKeys = &mTextKeys[groupname]; + mAnimVelocity = 0.0f; + + if(mNonAccumRoot) + calcAnimVelocity(); + + found = true; + break; + } + } + if(!found) + throw std::runtime_error("Failed to find animation "+groupname); + + reset(start, stop); + setLooping(loop); + mPlaying = true; + } + catch(std::exception &e) { + std::cerr<< e.what() <end() || mNextKey->first > targetTime) + { + movement += updatePosition(targetTime); + mPlaying = (mLooping || mStopTime > targetTime); + break; + } + + float time = mNextKey->first; + const std::string &evt = mNextKey->second; + mNextKey++; + + movement += updatePosition(time); + mPlaying = (mLooping || mStopTime > time); + + timepassed = targetTime - time; + + if(!handleEvent(time, evt)) + break; + } + + return movement; } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 07583db78..7caf35169 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,55 +1,99 @@ #ifndef _GAME_RENDER_ANIMATION_H #define _GAME_RENDER_ANIMATION_H -#include +#include -#include -#include -#include "../mwworld/actiontalk.hpp" -#include -#include +#include "../mwworld/ptr.hpp" +namespace MWMechanics +{ + class CharacterController; +} +namespace MWRender +{ - -namespace MWRender { - -class Animation { - struct GroupTimes { - float mStart; - float mStop; - float mLoopStart; - float mLoopStop; - - size_t mLoops; - - GroupTimes() - : mStart(-1.0f), mStop(-1.0f), mLoopStart(-1.0f), mLoopStop(-1.0f), - mLoops(0) - { } - }; - +class Animation +{ protected: + MWWorld::Ptr mPtr; + MWMechanics::CharacterController *mController; + Ogre::SceneNode* mInsert; - - float mTime; - GroupTimes mCurGroup; - GroupTimes mNextGroup; - - bool mSkipFrame; - NifOgre::EntityList mEntityList; - NifOgre::TextKeyMap mTextKeys; + std::map mTextKeys; + Ogre::Node *mAccumRoot; + Ogre::Bone *mNonAccumRoot; + Ogre::Vector3 mAccumulate; + Ogre::Vector3 mLastPosition; - bool findGroupTimes(const std::string &groupname, GroupTimes *times); + std::vector mSkeletonSources; + + NifOgre::TextKeyMap *mCurrentKeys; + NifOgre::TextKeyMap::const_iterator mNextKey; + Ogre::Animation *mCurrentAnim; + float mCurrentTime; + float mStopTime; + bool mPlaying; + bool mLooping; + + float mAnimVelocity; + float mAnimSpeedMult; + + void calcAnimVelocity(); + + /* Applies the given animation to the given skeleton instance, using the specified time. */ + void applyAnimation(const Ogre::Animation *anim, float time, Ogre::SkeletonInstance *skel); + + /* Updates a skeleton instance so that all bones matching the source skeleton (based on + * bone names) are positioned identically. */ + void updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel); + + /* Updates the animation to the specified time, and returns the movement + * vector since the last update or reset. */ + Ogre::Vector3 updatePosition(float time); + + /* Resets the animation to the time of the specified start marker, without + * moving anything, and set the end time to the specified stop marker. If + * the marker is not found, it resets to the beginning or end respectively. + */ + void reset(const std::string &start, const std::string &stop); + + bool handleEvent(float time, const std::string &evt); + + /* Specifies a list of skeleton names to use as animation sources. */ + void setAnimationSources(const std::vector &names); + + /* Specifies a single skeleton name to use as an animation source. */ + void setAnimationSource(const std::string &name) + { + std::vector names(1, name); + setAnimationSources(names); + } + + void createEntityList(Ogre::SceneNode *node, const std::string &model); public: - Animation(); + Animation(const MWWorld::Ptr &ptr); virtual ~Animation(); - void playGroup(std::string groupname, int mode, int loops); - void skipAnim(); - virtual void runAnimation(float timepassed); + void setController(MWMechanics::CharacterController *controller); + + void updatePtr(const MWWorld::Ptr &ptr); + + bool hasAnimation(const std::string &anim); + + // Specifies the axis' to accumulate on. Non-accumulated axis will just + // move visually, but not affect the actual movement. Each x/y/z value + // should be on the scale of 0 to 1. + void setAccumulation(const Ogre::Vector3 &accum); + + void setSpeed(float speed); + + void setLooping(bool loop); + + void play(const std::string &groupname, const std::string &start, const std::string &stop, bool loop); + virtual Ogre::Vector3 runAnimation(float timepassed); }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 0a11dc281..c99e42662 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -10,6 +11,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "renderconst.hpp" #include "npcanimation.hpp" @@ -35,16 +37,30 @@ namespace MWRender } - void CharacterPreview::setup (Ogre::SceneManager *sceneManager) + void CharacterPreview::setup () { - mSceneMgr = sceneManager; + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + /// \todo Read the fallback values from INIImporter (Inventory:Directional*) + Ogre::Light* l = mSceneMgr->createLight(); + l->setType (Ogre::Light::LT_DIRECTIONAL); + l->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); + l->setDiffuseColour (Ogre::ColourValue(1,1,1)); + + mSceneMgr->setAmbientLight (Ogre::ColourValue(0.5, 0.5, 0.5)); + mCamera = mSceneMgr->createCamera (mName); mCamera->setAspectRatio (float(mSizeX) / float(mSizeY)); - mNode = static_cast(mSceneMgr->getRootSceneNode()->getChild("mwRoot"))->createChildSceneNode (); + Ogre::SceneNode* renderRoot = mSceneMgr->getRootSceneNode()->createChildSceneNode("renderRoot"); + + //we do this with mwRoot in renderingManager, do it here too. + renderRoot->pitch(Ogre::Degree(-90)); + + mNode = renderRoot->createChildSceneNode(); mAnimation = new NpcAnimation(mCharacter, mNode, - MWWorld::Class::get(mCharacter).getInventoryStore (mCharacter), RV_PlayerPreview); + MWWorld::Class::get(mCharacter).getInventoryStore (mCharacter), 0, renderHeadOnly()); mNode->setVisible (false); @@ -66,8 +82,6 @@ namespace MWRender mViewport->setOverlaysEnabled(false); mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); mViewport->setShadowsEnabled(false); - mViewport->setMaterialScheme("local_map"); - mViewport->setVisibilityMask (RV_PlayerPreview); mRenderTarget->setActive(true); mRenderTarget->setAutoUpdated (false); @@ -79,6 +93,7 @@ namespace MWRender //Ogre::TextureManager::getSingleton().remove(mName); mSceneMgr->destroyCamera (mName); delete mAnimation; + Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); } void CharacterPreview::rebuild() @@ -87,7 +102,7 @@ namespace MWRender delete mAnimation; mAnimation = new NpcAnimation(mCharacter, mNode, - MWWorld::Class::get(mCharacter).getInventoryStore (mCharacter), RV_PlayerPreview); + MWWorld::Class::get(mCharacter).getInventoryStore (mCharacter), 0, renderHeadOnly()); mNode->setVisible (false); @@ -103,6 +118,7 @@ namespace MWRender InventoryPreview::InventoryPreview(MWWorld::Ptr character) : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) + , mSelectionBuffer(NULL) { } @@ -113,7 +129,8 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { - mAnimation->forceUpdate (); + mAnimation->forceUpdate(); + mAnimation->runAnimation(0.0f); mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); @@ -134,17 +151,17 @@ namespace MWRender void InventoryPreview::onSetup () { - mSelectionBuffer = new OEngine::Render::SelectionBuffer(mCamera, 512, 1024, RV_PlayerPreview); + if (!mSelectionBuffer) + mSelectionBuffer = new OEngine::Render::SelectionBuffer(mCamera, 512, 1024, 0); - mAnimation->playGroup ("inventoryhandtohand", 0, 1); - mAnimation->runAnimation (0); + mAnimation->play("inventoryhandtohand", "start", "stop", false); } // -------------------------------------------------------------------------------------------------- RaceSelectionPreview::RaceSelectionPreview() : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayer().getPlayer(), - 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 120, -35), Ogre::Vector3(0,125,0)) + 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0)) , mRef(&mBase) { mBase = *mCharacter.get()->mBase; @@ -153,8 +170,11 @@ namespace MWRender void RaceSelectionPreview::update(float angle) { + mAnimation->runAnimation(0.0f); mNode->roll(Ogre::Radian(angle), Ogre::SceneNode::TS_LOCAL); + updateCamera(); + mNode->setVisible (true); mRenderTarget->update(); mNode->setVisible (false); @@ -167,4 +187,21 @@ namespace MWRender rebuild(); update(0); } + + void RaceSelectionPreview::onSetup () + { + mAnimation->play("idle", "start", "stop", false); + + updateCamera(); + } + + void RaceSelectionPreview::updateCamera() + { + Ogre::Vector3 scale = mNode->getScale(); + Ogre::Vector3 headOffset = mAnimation->getHeadNode()->_getDerivedPosition(); + headOffset = mNode->convertLocalToWorldPosition(headOffset); + + mCamera->setPosition(headOffset + mPosition * scale); + mCamera->lookAt(headOffset + mPosition*Ogre::Vector3(0,1,0) * scale); + } } diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 18362d1db..08cbd5108 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -23,19 +23,21 @@ namespace MWRender class NpcAnimation; - class CharacterPreview : public ExternalRendering + class CharacterPreview { public: CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, Ogre::Vector3 position, Ogre::Vector3 lookAt); virtual ~CharacterPreview(); - virtual void setup (Ogre::SceneManager *sceneManager); + virtual void setup (); virtual void onSetup(); virtual void rebuild(); protected: + virtual bool renderHeadOnly() { return false; } + Ogre::TexturePtr mTexture; Ogre::RenderTarget* mRenderTarget; Ogre::Viewport* mViewport; @@ -82,9 +84,17 @@ namespace MWRender ESM::NPC mBase; MWWorld::LiveCellRef mRef; + protected: + + virtual bool renderHeadOnly() { return true; } + + void updateCamera(); + public: RaceSelectionPreview(); + virtual void onSetup(); + void update(float angle); const ESM::NPC &getPrototype() const { diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7ee361a6f..22f84ee01 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -8,70 +8,42 @@ #include "../mwbase/world.hpp" -using namespace Ogre; -using namespace NifOgre; -namespace MWRender{ +namespace MWRender +{ CreatureAnimation::~CreatureAnimation() { } -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr): Animation() +CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr) + : Animation(ptr) { - mInsert = ptr.getRefData().getBaseNode(); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef *ref = mPtr.get(); assert (ref->mBase != NULL); if(!ref->mBase->mModel.empty()) { - std::string mesh = "meshes\\" + ref->mBase->mModel; + std::string model = "meshes\\"+ref->mBase->mModel; - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, &mTextKeys, mesh); + createEntityList(mPtr.getRefData().getBaseNode(), model); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *ent = mEntityList.mEntities[i]; ent->setVisibilityFlags(RV_Actors); - bool transparent = false; - for (unsigned int j=0;j < ent->getNumSubEntities() && !transparent; ++j) + for(unsigned int j=0; j < ent->getNumSubEntities(); ++j) { - Ogre::MaterialPtr mat = ent->getSubEntity(j)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements() && !transparent) - { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements() && !transparent) - { - Ogre::Pass* pass = passIt.getNext(); - - if (pass->getDepthWriteEnabled() == false) - transparent = true; - } - } - } - ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - } - - if(mEntityList.mSkelBase) - { - Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); - while(as.hasMoreElements()) - { - Ogre::AnimationState *state = as.getNext(); - state->setEnabled(true); - state->setLoop(false); + Ogre::SubEntity* subEnt = ent->getSubEntity(j); + subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); } } + + std::vector names; + if((ref->mBase->mFlags&ESM::Creature::Biped)) + names.push_back("meshes\\base_anim.nif"); + names.push_back(model); + setAnimationSources(names); } } -void CreatureAnimation::runAnimation(float timepassed) -{ - // Placeholder - - Animation::runAnimation(timepassed); -} - } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index e1a7bbb8f..0c277d198 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -3,18 +3,19 @@ #include "animation.hpp" -#include "components/nifogre/ogre_nif_loader.hpp" +namespace MWWorld +{ + class Ptr; +} - -namespace MWRender{ - - class CreatureAnimation: public Animation +namespace MWRender +{ + class CreatureAnimation : public Animation { public: - virtual ~CreatureAnimation(); CreatureAnimation(const MWWorld::Ptr& ptr); - virtual void runAnimation(float timepassed); - + virtual ~CreatureAnimation(); }; } + #endif diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 1548cc1b0..54f288bff 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -148,9 +150,9 @@ ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid) return result; } -Debugging::Debugging(SceneNode *mwRoot, OEngine::Physic::PhysicEngine *engine) : - mMwRoot(mwRoot), mEngine(engine), - mSceneMgr(mwRoot->getCreator()), +Debugging::Debugging(SceneNode *root, OEngine::Physic::PhysicEngine *engine) : + mRootNode(root), mEngine(engine), + mSceneMgr(root->getCreator()), mPathgridEnabled(false), mInteriorPathgridNode(NULL), mPathGridRoot(NULL), mGridMatsCreated(false) @@ -206,7 +208,7 @@ void Debugging::togglePathgrid() createGridMaterials(); // add path grid meshes to already loaded cells - mPathGridRoot = mMwRoot->createChildSceneNode(); + mPathGridRoot = mRootNode->createChildSceneNode(); for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) { enableCellPathgrid(*it); diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp index d312b6d54..6a4eef58f 100644 --- a/apps/openmw/mwrender/debugging.hpp +++ b/apps/openmw/mwrender/debugging.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -13,6 +12,14 @@ namespace ESM struct Pathgrid; } +namespace OEngine +{ + namespace Physic + { + class PhysicEngine; + } +} + namespace Ogre { class Camera; @@ -47,7 +54,7 @@ namespace MWRender typedef std::vector CellList; CellList mActiveCells; - Ogre::SceneNode *mMwRoot; + Ogre::SceneNode *mRootNode; Ogre::SceneNode *mPathGridRoot; @@ -71,7 +78,7 @@ namespace MWRender Ogre::ManualObject *createPathgridLines(const ESM::Pathgrid *pathgrid); Ogre::ManualObject *createPathgridPoints(const ESM::Pathgrid *pathgrid); public: - Debugging(Ogre::SceneNode* mwRoot, OEngine::Physic::PhysicEngine *engine); + Debugging(Ogre::SceneNode* root, OEngine::Physic::PhysicEngine *engine); ~Debugging(); bool toggleRenderMode (int mode); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 072015f9a..055faaa1f 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -190,7 +190,7 @@ namespace MWRender { imageX = float(x / 8192.f - mMinX) / (mMaxX - mMinX + 1); - imageY = 1.f-float(-z / 8192.f - mMinY) / (mMaxY - mMinY + 1); + imageY = 1.f-float(z / 8192.f - mMinY) / (mMaxY - mMinY + 1); } void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index d878cb86e..3efcd08dd 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,6 +1,5 @@ #include "localmap.hpp" -#include #include #include @@ -28,11 +27,14 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag mCellCamera = mRendering->getScene()->createCamera("CellCamera"); mCellCamera->setProjectionType(PT_ORTHOGRAPHIC); - // look down -y - const float sqrt0pt5 = 0.707106781; - mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); mCameraNode->attachObject(mCellCamera); + + mLight = mRendering->getScene()->createLight(); + mLight->setType (Ogre::Light::LT_DIRECTIONAL); + mLight->setDirection (Ogre::Vector3(0.3, 0.3, -0.7)); + mLight->setVisible (false); + mLight->setDiffuseColour (ColourValue(0.7,0.7,0.7)); } LocalMap::~LocalMap() @@ -82,8 +84,8 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) } else { - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); - Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z); + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); + Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); Vector2 length = max-min; // divide into segments @@ -107,6 +109,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) mInterior = false; mCameraRotNode->setOrientation(Quaternion::IDENTITY); + mCellCamera->setOrientation(Quaternion(Ogre::Math::Cos(Ogre::Degree(0)/2.f), 0, 0, -Ogre::Math::Sin(Ogre::Degree(0)/2.f))); int x = cell->mCell->getGridX(); int y = cell->mCell->getGridY(); @@ -115,49 +118,60 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) mCameraPosNode->setPosition(Vector3(0,0,0)); - render((x+0.5)*sSize, (-y-0.5)*sSize, -10000, 10000, sSize, sSize, name); + render((x+0.5)*sSize, (y+0.5)*sSize, -10000, 10000, sSize, sSize, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, AxisAlignedBox bounds) { + // if we're in an empty cell, don't bother rendering anything + if (bounds.isNull ()) + return; + mInterior = true; mBounds = bounds; - Vector2 z(mBounds.getMaximum().y, mBounds.getMinimum().y); + float zMin = mBounds.getMinimum().z; + float zMax = mBounds.getMaximum().z; const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell); - Radian angle(std::atan2(-north.x, -north.y)); + Radian angle = Ogre::Math::ATan2 (north.x, north.y); mAngle = angle.valueRadians(); - mCameraRotNode->setOrientation(Quaternion(Math::Cos(angle/2.f), 0, Math::Sin(angle/2.f), 0)); + + mCellCamera->setOrientation(Quaternion::IDENTITY); + mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f))); // rotate the cell and merge the rotated corners to the bounding box - Vector2 _center(bounds.getCenter().x, bounds.getCenter().z); - Vector3 _c1 = bounds.getCorner(AxisAlignedBox::NEAR_LEFT_BOTTOM); - Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); - Vector3 _c3 = bounds.getCorner(AxisAlignedBox::NEAR_RIGHT_BOTTOM); - Vector3 _c4 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); - Vector2 c1(_c1.x, _c1.z); - Vector2 c2(_c2.x, _c2.z); - Vector2 c3(_c3.x, _c3.z); - Vector2 c4(_c4.x, _c4.z); + Vector2 _center(bounds.getCenter().x, bounds.getCenter().y); + Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); + Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); + Vector3 _c3 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_TOP); + Vector3 _c4 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_TOP); + + Vector2 c1(_c1.x, _c1.y); + Vector2 c2(_c2.x, _c2.y); + Vector2 c3(_c3.x, _c3.y); + Vector2 c4(_c4.x, _c4.y); c1 = rotatePoint(c1, _center, mAngle); c2 = rotatePoint(c2, _center, mAngle); c3 = rotatePoint(c3, _center, mAngle); c4 = rotatePoint(c4, _center, mAngle); - mBounds.merge(Vector3(c1.x, 0, c1.y)); - mBounds.merge(Vector3(c2.x, 0, c2.y)); - mBounds.merge(Vector3(c3.x, 0, c3.y)); - mBounds.merge(Vector3(c4.x, 0, c4.y)); + mBounds.merge(Vector3(c1.x, c1.y, 0)); + mBounds.merge(Vector3(c2.x, c2.y, 0)); + mBounds.merge(Vector3(c3.x, c3.y, 0)); + mBounds.merge(Vector3(c4.x, c4.y, 0)); - Vector2 center(mBounds.getCenter().x, mBounds.getCenter().z); + // apply a little padding + mBounds.scale ((mBounds.getSize ()+Ogre::Vector3(1000,1000,0)) / mBounds.getSize ()); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); - Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z); + Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y); + + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); + Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); Vector2 length = max-min; - mCameraPosNode->setPosition(Vector3(center.x, 0, center.y)); + mCameraPosNode->setPosition(Vector3(center.x, center.y, 0)); // divide into segments const int segsX = std::ceil( length.x / sSize ); @@ -172,7 +186,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, Vector2 start = min + Vector2(sSize*x,sSize*y); Vector2 newcenter = start + 4096; - render(newcenter.x - center.x, newcenter.y - center.y, z.y, z.x, sSize, sSize, + render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, cell->mCell->mName + "_" + coordStr(x,y)); } } @@ -182,22 +196,24 @@ void LocalMap::render(const float x, const float y, const float zlow, const float zhigh, const float xw, const float yw, const std::string& texture) { - // disable fog - // changing FOG_MODE is not a solution when using shaders, thus we have to push linear start/end - const float fStart = mRendering->getScene()->getFogStart(); - const float fEnd = mRendering->getScene()->getFogEnd(); - const ColourValue& clr = mRendering->getScene()->getFogColour(); - mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, 1000000, 10000000); - - // make everything visible - mRendering->getScene()->setAmbientLight(ColourValue(1,1,1)); - mRenderingManager->disableLights(); - - mCameraNode->setPosition(Vector3(x, zhigh+100000, y)); //mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); mCellCamera->setFarClipDistance(0); // infinite mCellCamera->setOrthoWindow(xw, yw); + mCameraNode->setPosition(Vector3(x, y, zhigh+100000)); + + // disable fog (only necessary for fixed function, the shader based + // materials already do this through local_map material configuration) + float oldFogStart = mRendering->getScene()->getFogStart(); + float oldFogEnd = mRendering->getScene()->getFogEnd(); + Ogre::ColourValue oldFogColour = mRendering->getScene()->getFogColour(); + mRendering->getScene()->setFog(FOG_NONE); + + // set up lighting + Ogre::ColourValue oldAmbient = mRendering->getScene()->getAmbientLight(); + mRendering->getScene()->setAmbientLight(Ogre::ColourValue(0.3, 0.3, 0.3)); + mRenderingManager->disableLights(true); + mLight->setVisible(true); TexturePtr tex; // try loading from memory @@ -222,14 +238,13 @@ void LocalMap::render(const float x, const float y, TU_RENDERTARGET); RenderTarget* rtt = tex->getBuffer()->getRenderTarget(); + rtt->setAutoUpdated(false); Viewport* vp = rtt->addViewport(mCellCamera); vp->setOverlaysEnabled(false); vp->setShadowsEnabled(false); vp->setBackgroundColour(ColourValue(0, 0, 0)); vp->setVisibilityMask(RV_Map); - - // use fallback techniques without shadows and without mrt vp->setMaterialScheme("local_map"); rtt->update(); @@ -263,24 +278,25 @@ void LocalMap::render(const float x, const float y, //rtt->writeContentsToFile("./" + texture + ".jpg"); } } - - mRenderingManager->enableLights(); + mRenderingManager->enableLights(true); + mLight->setVisible(false); // re-enable fog - mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); + mRendering->getScene()->setFog(FOG_LINEAR, oldFogColour, 0, oldFogStart, oldFogEnd); + mRendering->getScene()->setAmbientLight(oldAmbient); } void LocalMap::getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y) { - pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().z), mAngle); + pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), mAngle); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); x = std::ceil((pos.x - min.x)/sSize)-1; y = std::ceil((pos.y - min.y)/sSize)-1; nX = (pos.x - min.x - sSize*x)/sSize; - nY = (pos.y - min.y - sSize*y)/sSize; + nY = 1.0-(pos.y - min.y - sSize*y)/sSize; } bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior) @@ -311,19 +327,19 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni int x,y; float u,v; - Vector2 pos(position.x, position.z); + Vector2 pos(position.x, position.y); if (mInterior) getInteriorMapPosition(pos, u,v, x,y); - Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).zAxis(); + Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis(); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); if (!mInterior) { x = std::ceil(pos.x / sSize)-1; - y = std::ceil(-pos.y / sSize)-1; + y = std::ceil(pos.y / sSize)-1; mCellX = x; mCellY = y; } @@ -337,7 +353,7 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni if (!mInterior) { u = std::abs((pos.x - (sSize*x))/sSize); - v = 1-std::abs((pos.y + (sSize*y))/sSize); + v = 1.0-std::abs((pos.y - (sSize*y))/sSize); texBaseName = "Cell_"; } else @@ -346,15 +362,13 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni } MWBase::Environment::get().getWindowManager()->setPlayerPos(u, v); - MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, -playerdirection.z); + MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, playerdirection.y); // explore radius (squared) const float sqrExploreRadius = (mInterior ? 0.01 : 0.09) * sFogOfWarResolution*sFogOfWarResolution; const float exploreRadius = (mInterior ? 0.1 : 0.3) * sFogOfWarResolution; // explore radius from 0 to sFogOfWarResolution const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) - int intExtMult = mInterior ? 1 : -1; // interior and exterior have reversed Y coordinates (interior: top to bottom) - // change the affected fog of war textures (in a 3x3 grid around the player) for (int mx = -1; mx<2; ++mx) { @@ -375,7 +389,7 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni if (!affected) continue; - std::string texName = texBaseName + coordStr(x+mx,y+my*intExtMult); + std::string texName = texBaseName + coordStr(x+mx,y+my*-1); TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); if (!tex.isNull()) diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 1aedf1325..72e637d9a 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace MWWorld { @@ -90,6 +91,9 @@ namespace MWRender Ogre::SceneNode* mCameraPosNode; Ogre::SceneNode* mCameraRotNode; + // directional light from a fixed angle + Ogre::Light* mLight; + float mAngle; const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e6a8006e2..9da70beb4 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -5,295 +5,247 @@ #include #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "renderconst.hpp" -using namespace Ogre; -using namespace NifOgre; -namespace MWRender{ +namespace MWRender +{ + +const NpcAnimation::PartInfo NpcAnimation::sPartList[NpcAnimation::sPartListSize] = { + { ESM::PRT_Head, "Head" }, + { ESM::PRT_Hair, "Head" }, + { ESM::PRT_Neck, "Neck" }, + { ESM::PRT_Cuirass, "Chest" }, + { ESM::PRT_Groin, "Groin" }, + { ESM::PRT_Skirt, "Groin" }, + { ESM::PRT_RHand, "Right Hand" }, + { ESM::PRT_LHand, "Left Hand" }, + { ESM::PRT_RWrist, "Right Wrist" }, + { ESM::PRT_LWrist, "Left Wrist" }, + { ESM::PRT_Shield, "Shield" }, + { ESM::PRT_RForearm, "Right Forearm" }, + { ESM::PRT_LForearm, "Left Forearm" }, + { ESM::PRT_RUpperarm, "Right Upper Arm" }, + { ESM::PRT_LUpperarm, "Left Upper Arm" }, + { ESM::PRT_RFoot, "Right Foot" }, + { ESM::PRT_LFoot, "Left Foot" }, + { ESM::PRT_RAnkle, "Right Ankle" }, + { ESM::PRT_LAnkle, "Left Ankle" }, + { ESM::PRT_RKnee, "Right Knee" }, + { ESM::PRT_LKnee, "Left Knee" }, + { ESM::PRT_RLeg, "Right Upper Leg" }, + { ESM::PRT_LLeg, "Left Upper Leg" }, + { ESM::PRT_RPauldron, "Right Clavicle" }, + { ESM::PRT_LPauldron, "Left Clavicle" }, + { ESM::PRT_Weapon, "Weapon" }, + { ESM::PRT_Tail, "Tail" } +}; + NpcAnimation::~NpcAnimation() { - removeEntities(mHead); - removeEntities(mHair); - removeEntities(mNeck); - removeEntities(mChest); - removeEntities(mGroin); - removeEntities(mSkirt); - removeEntities(mHandL); - removeEntities(mHandR); - removeEntities(mWristL); - removeEntities(mWristR); - removeEntities(mForearmL); - removeEntities(mForearmR); - removeEntities(mUpperArmL); - removeEntities(mUpperArmR); - removeEntities(mFootL); - removeEntities(mFootR); - removeEntities(mAnkleL); - removeEntities(mAnkleR); - removeEntities(mKneeL); - removeEntities(mKneeR); - removeEntities(mUpperLegL); - removeEntities(mUpperLegR); - removeEntities(mClavicleL); - removeEntities(mClavicleR); - removeEntities(mTail); + for(size_t i = 0;i < sPartListSize;i++) + removeEntities(mEntityParts[i]); } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags) - : Animation(), +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags, bool headOnly) + : Animation(ptr), mStateID(-1), - mInv(inv), mTimeToChange(0), mVisibilityFlags(visibilityFlags), - mRobe(mInv.end()), - mHelmet(mInv.end()), - mShirt(mInv.end()), - mCuirass(mInv.end()), - mGreaves(mInv.end()), - mPauldronL(mInv.end()), - mPauldronR(mInv.end()), - mBoots(mInv.end()), - mPants(mInv.end()), - mGloveL(mInv.end()), - mGloveR(mInv.end()), - mSkirtIter(mInv.end()) + mRobe(inv.end()), + mHelmet(inv.end()), + mShirt(inv.end()), + mCuirass(inv.end()), + mGreaves(inv.end()), + mPauldronL(inv.end()), + mPauldronR(inv.end()), + mBoots(inv.end()), + mPants(inv.end()), + mGloveL(inv.end()), + mGloveR(inv.end()), + mSkirtIter(inv.end()), + mHeadOnly(headOnly) { - mNpc = ptr.get()->mBase; + mNpc = mPtr.get()->mBase; - for (int init = 0; init < 27; init++) + for(size_t i = 0;i < sPartListSize;i++) { - mPartslots[init] = -1; //each slot is empty - mPartPriorities[init] = 0; + mPartslots[i] = -1; //each slot is empty + mPartPriorities[i] = 0; } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); + float scale = race->mData.mHeight.mMale; + if(!mNpc->isMale()) + scale = race->mData.mHeight.mFemale; + node->scale(Ogre::Vector3(scale)); + mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; mBodyPrefix = "b_n_" + mNpc->mRace; - std::transform(mBodyPrefix.begin(), mBodyPrefix.end(), mBodyPrefix.begin(), ::tolower); - - mInsert = node; - assert(mInsert); + Misc::StringUtils::toLower(mBodyPrefix); bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, &mTextKeys, smodel); + createEntityList(node, smodel); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *base = mEntityList.mEntities[i]; - base->getUserObjectBindings ().setUserAny (Ogre::Any(-1)); + base->getUserObjectBindings().setUserAny(Ogre::Any(-1)); + if (mVisibilityFlags != 0) + base->setVisibilityFlags(mVisibilityFlags); - base->setVisibilityFlags(mVisibilityFlags); - bool transparent = false; - for(unsigned int j=0;j < base->getNumSubEntities();++j) + for(unsigned int j=0; j < base->getNumSubEntities(); ++j) { - Ogre::MaterialPtr mat = base->getSubEntity(j)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements()) - { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements()) - { - Ogre::Pass* pass = passIt.getNext(); - if (pass->getDepthWriteEnabled() == false) - transparent = true; - } - } - } - base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - } - - if(mEntityList.mSkelBase) - { - Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); - while(as.hasMoreElements()) - { - Ogre::AnimationState *state = as.getNext(); - state->setEnabled(true); - state->setLoop(false); + Ogre::SubEntity* subEnt = base->getSubEntity(j); + subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); } } - float scale = race->mData.mHeight.mMale; - if (!mNpc->isMale()) { - scale = race->mData.mHeight.mFemale; - } - mInsert->scale(scale, scale, scale); + std::vector skelnames(1, smodel); + if(!mNpc->isMale() && !isBeast) + skelnames.push_back("meshes\\base_anim_female.nif"); + else if(mBodyPrefix.find("argonian") != std::string::npos) + skelnames.push_back("meshes\\argonian_swimkna.nif"); + if(mNpc->mModel.length() > 0) + skelnames.push_back("meshes\\"+Misc::StringUtils::lowerCase(mNpc->mModel)); + setAnimationSources(skelnames); - updateParts(); + updateParts(true); } -void NpcAnimation::updateParts() +void NpcAnimation::updateParts(bool forceupdate) { - bool apparelChanged = false; + static const struct { + int numRemoveParts; // Max: 1 + ESM::PartReferenceType removeParts[1]; - const struct { - MWWorld::ContainerStoreIterator *iter; + MWWorld::ContainerStoreIterator NpcAnimation::*part; int slot; + + int numReserveParts; // Max: 12 + ESM::PartReferenceType reserveParts[12]; } slotlist[] = { - { &mRobe, MWWorld::InventoryStore::Slot_Robe }, - { &mSkirtIter, MWWorld::InventoryStore::Slot_Skirt }, - { &mHelmet, MWWorld::InventoryStore::Slot_Helmet }, - { &mCuirass, MWWorld::InventoryStore::Slot_Cuirass }, - { &mGreaves, MWWorld::InventoryStore::Slot_Greaves }, - { &mPauldronL, MWWorld::InventoryStore::Slot_LeftPauldron }, - { &mPauldronR, MWWorld::InventoryStore::Slot_RightPauldron }, - { &mBoots, MWWorld::InventoryStore::Slot_Boots }, - { &mGloveL, MWWorld::InventoryStore::Slot_LeftGauntlet }, - { &mGloveR, MWWorld::InventoryStore::Slot_RightGauntlet }, - { &mShirt, MWWorld::InventoryStore::Slot_Shirt }, - { &mPants, MWWorld::InventoryStore::Slot_Pants }, + { 0, { }, + &NpcAnimation::mRobe, MWWorld::InventoryStore::Slot_Robe, + 12, { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, + ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, + ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_RPauldron, ESM::PRT_LPauldron } + }, + + { 0, { }, + &NpcAnimation::mSkirtIter, MWWorld::InventoryStore::Slot_Skirt, + 3, { ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg } + }, + + { 1, { ESM::PRT_Hair }, + &NpcAnimation::mHelmet, MWWorld::InventoryStore::Slot_Helmet, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mCuirass, MWWorld::InventoryStore::Slot_Cuirass, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mGreaves, MWWorld::InventoryStore::Slot_Greaves, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mPauldronL, MWWorld::InventoryStore::Slot_LeftPauldron, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mPauldronR, MWWorld::InventoryStore::Slot_RightPauldron, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mBoots, MWWorld::InventoryStore::Slot_Boots, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mGloveL, MWWorld::InventoryStore::Slot_LeftGauntlet, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mGloveR, MWWorld::InventoryStore::Slot_RightGauntlet, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mShirt, MWWorld::InventoryStore::Slot_Shirt, + 0, { } + }, + + { 0, { }, + &NpcAnimation::mPants, MWWorld::InventoryStore::Slot_Pants, + 0, { } + }, }; - for(size_t i = 0;i < sizeof(slotlist)/sizeof(slotlist[0]);i++) + static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); + + MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + for(size_t i = 0;!forceupdate && i < slotlistsize;i++) { - MWWorld::ContainerStoreIterator iter = mInv.getSlot(slotlist[i].slot); - if(*slotlist[i].iter != iter) + MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].slot); + if(this->*slotlist[i].part != iter) { - *slotlist[i].iter = iter; - removePartGroup(slotlist[i].slot); - apparelChanged = true; + forceupdate = true; + break; } } + if(!forceupdate) + return; - if(apparelChanged) + for(size_t i = 0;i < slotlistsize && !mHeadOnly;i++) { - if(mRobe != mInv.end()) - { - MWWorld::Ptr ptr = *mRobe; + MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].slot); - const ESM::Clothing *clothes = (ptr.get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Robe, 5, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_Skirt, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RPauldron, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + this->*slotlist[i].part = iter; + removePartGroup(slotlist[i].slot); + + if(this->*slotlist[i].part == inv.end()) + continue; + + for(int rem = 0;rem < slotlist[i].numRemoveParts;rem++) + removeIndividualPart(slotlist[i].removeParts[rem]); + + int prio = 1; + MWWorld::ContainerStoreIterator &store = this->*slotlist[i].part; + if(store->getTypeName() == typeid(ESM::Clothing).name()) + { + prio = ((slotlist[i].numReserveParts+1)<<1) + 0; + const ESM::Clothing *clothes = store->get()->mBase; + addPartGroup(slotlist[i].slot, prio, clothes->mParts.mParts); } - if(mSkirtIter != mInv.end()) + else if(store->getTypeName() == typeid(ESM::Armor).name()) { - MWWorld::Ptr ptr = *mSkirtIter; - - const ESM::Clothing *clothes = (ptr.get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Skirt, 4, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Skirt, 4); + prio = ((slotlist[i].numReserveParts+1)<<1) + 1; + const ESM::Armor *armor = store->get()->mBase; + addPartGroup(slotlist[i].slot, prio, armor->mParts.mParts); } - if(mHelmet != mInv.end()) - { - removeIndividualPart(ESM::PRT_Hair); - const ESM::Armor *armor = (mHelmet->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Helmet, 3, parts); - } - if(mCuirass != mInv.end()) - { - const ESM::Armor *armor = (mCuirass->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Cuirass, 3, parts); - } - if(mGreaves != mInv.end()) - { - const ESM::Armor *armor = (mGreaves->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Greaves, 3, parts); - } - - if(mPauldronL != mInv.end()) - { - const ESM::Armor *armor = (mPauldronL->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftPauldron, 3, parts); - } - if(mPauldronR != mInv.end()) - { - const ESM::Armor *armor = (mPauldronR->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); - } - if(mBoots != mInv.end()) - { - if(mBoots->getTypeName() == typeid(ESM::Clothing).name()) - { - const ESM::Clothing *clothes = (mBoots->get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 2, parts); - } - else if(mBoots->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = (mBoots->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 3, parts); - } - } - if(mGloveL != mInv.end()) - { - if(mGloveL->getTypeName() == typeid(ESM::Clothing).name()) - { - const ESM::Clothing *clothes = (mGloveL->get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (mGloveL->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 3, parts); - } - } - if(mGloveR != mInv.end()) - { - if(mGloveR->getTypeName() == typeid(ESM::Clothing).name()) - { - const ESM::Clothing *clothes = (mGloveR->get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (mGloveR->get())->mBase; - std::vector parts = armor->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 3, parts); - } - - } - - if(mShirt != mInv.end()) - { - const ESM::Clothing *clothes = (mShirt->get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Shirt, 2, parts); - } - if(mPants != mInv.end()) - { - const ESM::Clothing *clothes = (mPants->get())->mBase; - std::vector parts = clothes->mParts.mParts; - addPartGroup(MWWorld::InventoryStore::Slot_Pants, 2, parts); - } + for(int res = 0;res < slotlist[i].numReserveParts;res++) + reserveIndividualPart(slotlist[i].reserveParts[res], slotlist[i].slot, prio); } if(mPartPriorities[ESM::PRT_Head] < 1) @@ -301,6 +253,9 @@ void NpcAnimation::updateParts() if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1) addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); + if (mHeadOnly) + return; + static const struct { ESM::PartReferenceType type; const char name[2][12]; @@ -333,21 +288,18 @@ void NpcAnimation::updateParts() if(mPartPriorities[PartTypeList[i].type] < 1) { const ESM::BodyPart *part = NULL; - const MWWorld::Store &partStore = - store.get(); + const MWWorld::Store &partStore = store.get(); - if (!mNpc->isMale()) { + if(!mNpc->isMale()) + { part = partStore.search(mBodyPrefix + "_f_" + PartTypeList[i].name[0]); - if (part == 0) { + if(part == 0) part = partStore.search(mBodyPrefix + "_f_" + PartTypeList[i].name[1]); - } } - if (part == 0) { + if(part == 0) part = partStore.search(mBodyPrefix + "_m_" + PartTypeList[i].name[0]); - } - if (part == 0) { + if(part == 0) part = partStore.search(mBodyPrefix + "_m_" + PartTypeList[i].name[1]); - } if(part) addOrReplaceIndividualPart(PartTypeList[i].type, -1,1, "meshes\\"+part->mModel); @@ -357,27 +309,58 @@ void NpcAnimation::updateParts() NifOgre::EntityList NpcAnimation::insertBoundedPart(const std::string &mesh, int group, const std::string &bonename) { - NifOgre::EntityList entities = NIFLoader::createEntities(mEntityList.mSkelBase, bonename, - mInsert, mesh); + NifOgre::EntityList entities = NifOgre::Loader::createEntities(mEntityList.mSkelBase, bonename, + mInsert, mesh); std::vector &parts = entities.mEntities; for(size_t i = 0;i < parts.size();i++) { - parts[i]->setVisibilityFlags(mVisibilityFlags); - parts[i]->getUserObjectBindings ().setUserAny (Ogre::Any(group)); + parts[i]->getUserObjectBindings().setUserAny(Ogre::Any(group)); + if (mVisibilityFlags != 0) + parts[i]->setVisibilityFlags(mVisibilityFlags); + + for(unsigned int j=0; j < parts[i]->getNumSubEntities(); ++j) + { + Ogre::SubEntity* subEnt = parts[i]->getSubEntity(j); + subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); + } + } + if(entities.mSkelBase) + { + Ogre::AnimationStateSet *aset = entities.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); + while(asiter.hasMoreElements()) + { + Ogre::AnimationState *state = asiter.getNext(); + state->setEnabled(false); + state->setLoop(false); + } + Ogre::SkeletonInstance *skelinst = entities.mSkelBase->getSkeleton(); + Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); + while(boneiter.hasMoreElements()) + boneiter.getNext()->setManuallyControlled(true); } return entities; } -void NpcAnimation::runAnimation(float timepassed) +Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { - if(mTimeToChange > .2) + if(mTimeToChange <= 0.0f) { - mTimeToChange = 0; + mTimeToChange = 0.2f; updateParts(); } - mTimeToChange += timepassed; + mTimeToChange -= timepassed; - Animation::runAnimation(timepassed); + Ogre::Vector3 ret = Animation::runAnimation(timepassed); + const Ogre::SkeletonInstance *skelsrc = mEntityList.mSkelBase->getSkeleton(); + for(size_t i = 0;i < sPartListSize;i++) + { + Ogre::Entity *ent = mEntityParts[i].mSkelBase; + if(!ent) continue; + updateSkeletonInstance(skelsrc, ent->getSkeleton()); + ent->getAllAnimationStates()->_notifyDirty(); + } + return ret; } void NpcAnimation::removeEntities(NifOgre::EntityList &entities) @@ -399,62 +382,14 @@ void NpcAnimation::removeIndividualPart(int type) mPartPriorities[type] = 0; mPartslots[type] = -1; - if(type == ESM::PRT_Head) //0 - removeEntities(mHead); - else if(type == ESM::PRT_Hair) //1 - removeEntities(mHair); - else if(type == ESM::PRT_Neck) //2 - removeEntities(mNeck); - else if(type == ESM::PRT_Cuirass)//3 - removeEntities(mChest); - else if(type == ESM::PRT_Groin)//4 - removeEntities(mGroin); - else if(type == ESM::PRT_Skirt)//5 - removeEntities(mSkirt); - else if(type == ESM::PRT_RHand)//6 - removeEntities(mHandR); - else if(type == ESM::PRT_LHand)//7 - removeEntities(mHandL); - else if(type == ESM::PRT_RWrist)//8 - removeEntities(mWristR); - else if(type == ESM::PRT_LWrist) //9 - removeEntities(mWristL); - else if(type == ESM::PRT_Shield) //10 + for(size_t i = 0;i < sPartListSize;i++) { + if(type == sPartList[i].type) + { + removeEntities(mEntityParts[i]); + break; + } } - else if(type == ESM::PRT_RForearm) //11 - removeEntities(mForearmR); - else if(type == ESM::PRT_LForearm) //12 - removeEntities(mForearmL); - else if(type == ESM::PRT_RUpperarm) //13 - removeEntities(mUpperArmR); - else if(type == ESM::PRT_LUpperarm) //14 - removeEntities(mUpperArmL); - else if(type == ESM::PRT_RFoot) //15 - removeEntities(mFootR); - else if(type == ESM::PRT_LFoot) //16 - removeEntities(mFootL); - else if(type == ESM::PRT_RAnkle) //17 - removeEntities(mAnkleR); - else if(type == ESM::PRT_LAnkle) //18 - removeEntities(mAnkleL); - else if(type == ESM::PRT_RKnee) //19 - removeEntities(mKneeR); - else if(type == ESM::PRT_LKnee) //20 - removeEntities(mKneeL); - else if(type == ESM::PRT_RLeg) //21 - removeEntities(mUpperLegR); - else if(type == ESM::PRT_LLeg) //22 - removeEntities(mUpperLegL); - else if(type == ESM::PRT_RPauldron) //23 - removeEntities(mClavicleR); - else if(type == ESM::PRT_LPauldron) //24 - removeEntities(mClavicleL); - else if(type == ESM::PRT_Weapon) //25 - { - } - else if(type == ESM::PRT_Tail) //26 - removeEntities(mTail); } void NpcAnimation::reserveIndividualPart(int type, int group, int priority) @@ -484,96 +419,23 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, removeIndividualPart(type); mPartslots[type] = group; mPartPriorities[type] = priority; - switch(type) + + for(size_t i = 0;i < sPartListSize;i++) { - case ESM::PRT_Head: //0 - mHead = insertBoundedPart(mesh, group, "Head"); - break; - case ESM::PRT_Hair: //1 - mHair = insertBoundedPart(mesh, group, "Head"); - break; - case ESM::PRT_Neck: //2 - mNeck = insertBoundedPart(mesh, group, "Neck"); - break; - case ESM::PRT_Cuirass: //3 - mChest = insertBoundedPart(mesh, group, "Chest"); - break; - case ESM::PRT_Groin: //4 - mGroin = insertBoundedPart(mesh, group, "Groin"); - break; - case ESM::PRT_Skirt: //5 - mSkirt = insertBoundedPart(mesh, group, "Groin"); - break; - case ESM::PRT_RHand: //6 - mHandR = insertBoundedPart(mesh, group, "Right Hand"); - break; - case ESM::PRT_LHand: //7 - mHandL = insertBoundedPart(mesh, group, "Left Hand"); - break; - case ESM::PRT_RWrist: //8 - mWristR = insertBoundedPart(mesh, group, "Right Wrist"); - break; - case ESM::PRT_LWrist: //9 - mWristL = insertBoundedPart(mesh, group, "Left Wrist"); - break; - case ESM::PRT_Shield: //10 - break; - case ESM::PRT_RForearm: //11 - mForearmR = insertBoundedPart(mesh, group, "Right Forearm"); - break; - case ESM::PRT_LForearm: //12 - mForearmL = insertBoundedPart(mesh, group, "Left Forearm"); - break; - case ESM::PRT_RUpperarm: //13 - mUpperArmR = insertBoundedPart(mesh, group, "Right Upper Arm"); - break; - case ESM::PRT_LUpperarm: //14 - mUpperArmL = insertBoundedPart(mesh, group, "Left Upper Arm"); - break; - case ESM::PRT_RFoot: //15 - mFootR = insertBoundedPart(mesh, group, "Right Foot"); - break; - case ESM::PRT_LFoot: //16 - mFootL = insertBoundedPart(mesh, group, "Left Foot"); - break; - case ESM::PRT_RAnkle: //17 - mAnkleR = insertBoundedPart(mesh, group, "Right Ankle"); - break; - case ESM::PRT_LAnkle: //18 - mAnkleL = insertBoundedPart(mesh, group, "Left Ankle"); - break; - case ESM::PRT_RKnee: //19 - mKneeR = insertBoundedPart(mesh, group, "Right Knee"); - break; - case ESM::PRT_LKnee: //20 - mKneeL = insertBoundedPart(mesh, group, "Left Knee"); - break; - case ESM::PRT_RLeg: //21 - mUpperLegR = insertBoundedPart(mesh, group, "Right Upper Leg"); - break; - case ESM::PRT_LLeg: //22 - mUpperLegL = insertBoundedPart(mesh, group, "Left Upper Leg"); - break; - case ESM::PRT_RPauldron: //23 - mClavicleR = insertBoundedPart(mesh , group, "Right Clavicle"); - break; - case ESM::PRT_LPauldron: //24 - mClavicleL = insertBoundedPart(mesh, group, "Left Clavicle"); - break; - case ESM::PRT_Weapon: //25 - break; - case ESM::PRT_Tail: //26 - mTail = insertBoundedPart(mesh, group, "Tail"); + if(type == sPartList[i].type) + { + mEntityParts[i] = insertBoundedPart(mesh, group, sPartList[i].name); break; + } } return true; } -void NpcAnimation::addPartGroup(int group, int priority, std::vector &parts) +void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts) { for(std::size_t i = 0; i < parts.size(); i++) { - ESM::PartReference &part = parts[i]; + const ESM::PartReference &part = parts[i]; const MWWorld::Store &partStore = MWBase::Environment::get().getWorld()->getStore().get(); @@ -585,15 +447,15 @@ void NpcAnimation::addPartGroup(int group, int priority, std::vectormModel); + addOrReplaceIndividualPart(part.mPart, group, priority, "meshes\\"+bodypart->mModel); else reserveIndividualPart(part.mPart, group, priority); } } -void NpcAnimation::forceUpdate () +Ogre::Node* NpcAnimation::getHeadNode() { - updateParts(); + return mEntityList.mSkelBase->getSkeleton()->getBone("Bip01 Head"); } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index ca76dcc22..5da4afef8 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -3,9 +3,6 @@ #include "animation.hpp" -#include "components/nifogre/ogre_nif_loader.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwclass/npc.hpp" #include "../mwworld/containerstore.hpp" namespace ESM @@ -13,48 +10,36 @@ namespace ESM struct NPC; } -namespace MWRender{ +namespace MWWorld +{ + class InventoryStore; +} + +namespace MWRender +{ + +class NpcAnimation : public Animation +{ +public: +struct PartInfo { + ESM::PartReferenceType type; + const char name[32]; +}; -class NpcAnimation: public Animation{ private: - MWWorld::InventoryStore& mInv; + static const size_t sPartListSize = 27; + static const PartInfo sPartList[sPartListSize]; + int mStateID; - int mPartslots[27]; //Each part slot is taken by clothing, armor, or is empty - int mPartPriorities[27]; - - //Bounded Parts - NifOgre::EntityList mClavicleL; - NifOgre::EntityList mClavicleR; - NifOgre::EntityList mUpperArmL; - NifOgre::EntityList mUpperArmR; - NifOgre::EntityList mUpperLegL; - NifOgre::EntityList mUpperLegR; - NifOgre::EntityList mForearmL; - NifOgre::EntityList mForearmR; - NifOgre::EntityList mWristL; - NifOgre::EntityList mWristR; - NifOgre::EntityList mKneeR; - NifOgre::EntityList mKneeL; - NifOgre::EntityList mNeck; - NifOgre::EntityList mAnkleL; - NifOgre::EntityList mAnkleR; - NifOgre::EntityList mGroin; - NifOgre::EntityList mSkirt; - NifOgre::EntityList mFootL; - NifOgre::EntityList mFootR; - NifOgre::EntityList mHair; - NifOgre::EntityList mHandL; - NifOgre::EntityList mHandR; - NifOgre::EntityList mHead; - NifOgre::EntityList mChest; - NifOgre::EntityList mTail; + // Bounded Parts + NifOgre::EntityList mEntityParts[sPartListSize]; const ESM::NPC *mNpc; std::string mHeadModel; std::string mHairModel; std::string mBodyPrefix; - + bool mHeadOnly; float mTimeToChange; MWWorld::ContainerStoreIterator mRobe; @@ -72,23 +57,34 @@ private: int mVisibilityFlags; -public: - NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, - MWWorld::InventoryStore& inv, int visibilityFlags); - virtual ~NpcAnimation(); + int mPartslots[sPartListSize]; //Each part slot is taken by clothing, armor, or is empty + int mPartPriorities[sPartListSize]; + NifOgre::EntityList insertBoundedPart(const std::string &mesh, int group, const std::string &bonename); - virtual void runAnimation(float timepassed); - void updateParts(); + + void updateParts(bool forceupdate = false); + void removeEntities(NifOgre::EntityList &entities); void removeIndividualPart(int type); void reserveIndividualPart(int type, int group, int priority); bool addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh); void removePartGroup(int group); - void addPartGroup(int group, int priority, std::vector& parts); + void addPartGroup(int group, int priority, const std::vector &parts); - void forceUpdate(); +public: + NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, + MWWorld::InventoryStore& inv, int visibilityFlags, bool headOnly=false); + virtual ~NpcAnimation(); + + virtual Ogre::Vector3 runAnimation(float timepassed); + + Ogre::Node* getHeadNode(); + + void forceUpdate() + { updateParts(true); } }; } + #endif diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 36f09e6d9..cb1dfa75b 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include "../mwworld/ptr.hpp" @@ -17,7 +17,7 @@ using namespace MWRender; -// These are the Morrowind.ini defaults +/// \todo Replace these, once fallback values from the ini file are available. float Objects::lightLinearValue = 3; float Objects::lightLinearRadiusMult = 1; @@ -31,23 +31,40 @@ int Objects::uniqueID = 0; void Objects::clearSceneNode (Ogre::SceneNode *node) { - /// \todo This should probably be moved into OpenEngine at some point. for (int i=node->numAttachedObjects()-1; i>=0; --i) { Ogre::MovableObject *object = node->getAttachedObject (i); + + // for entities, destroy any objects attached to bones + if (object->getTypeFlags () == Ogre::SceneManager::ENTITY_TYPE_MASK) + { + Ogre::Entity* ent = static_cast(object); + Ogre::Entity::ChildObjectListIterator children = ent->getAttachedObjectIterator (); + while (children.hasMoreElements()) + { + mRenderer.getScene ()->destroyMovableObject (children.getNext ()); + } + } + node->detachObject (object); mRenderer.getScene()->destroyMovableObject (object); } + + Ogre::Node::ChildNodeIterator it = node->getChildIterator (); + while (it.hasMoreElements ()) + { + clearSceneNode(static_cast(it.getNext ())); + } } -void Objects::setMwRoot(Ogre::SceneNode* root) +void Objects::setRootNode(Ogre::SceneNode* root) { - mMwRoot = root; + mRootNode = root; } void Objects::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_) { - Ogre::SceneNode* root = mMwRoot; + Ogre::SceneNode* root = mRootNode; Ogre::SceneNode* cellnode; if(mCellSceneNodes.find(ptr.getCell()) == mCellSceneNodes.end()) { @@ -88,20 +105,16 @@ void Objects::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_) mIsStatic = static_; } -void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) +void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh, bool light) { Ogre::SceneNode* insert = ptr.getRefData().getBaseNode(); assert(insert); Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(insert, NULL, mesh); + NifOgre::EntityList entities = NifOgre::Loader::createEntities(insert, mesh); for(size_t i = 0;i < entities.mEntities.size();i++) - { - const Ogre::AxisAlignedBox &tmp = entities.mEntities[i]->getBoundingBox(); - bounds.merge(Ogre::AxisAlignedBox(insert->_getDerivedPosition() + tmp.getMinimum(), - insert->_getDerivedPosition() + tmp.getMaximum()) - ); - } + bounds.merge(entities.mEntities[i]->getWorldBoundingBox(true)); + Ogre::Vector3 extents = bounds.getSize(); extents *= insert->getScale(); float size = std::max(std::max(extents.x, extents.y), extents.z); @@ -116,38 +129,28 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; mBounds[ptr.getCell()].merge(bounds); - bool transparent = false; - for(size_t i = 0;i < entities.mEntities.size();i++) + bool anyTransparency = false; + for(size_t i = 0;!anyTransparency && i < entities.mEntities.size();i++) { Ogre::Entity *ent = entities.mEntities[i]; - for (unsigned int i=0; igetNumSubEntities(); ++i) + for(unsigned int i=0;!anyTransparency && i < ent->getNumSubEntities(); ++i) { - Ogre::MaterialPtr mat = ent->getSubEntity(i)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements()) - { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements()) - { - Ogre::Pass* pass = passIt.getNext(); - - if (pass->getDepthWriteEnabled() == false) - transparent = true; - } - } + anyTransparency = ent->getSubEntity(i)->getMaterial()->isTransparent(); } } - if(!mIsStatic || !Settings::Manager::getBool("use static geometry", "Objects") || transparent) + if(!mIsStatic || !Settings::Manager::getBool("use static geometry", "Objects") || anyTransparency) { for(size_t i = 0;i < entities.mEntities.size();i++) { Ogre::Entity *ent = entities.mEntities[i]; - + for(unsigned int i=0; i < ent->getNumSubEntities(); ++i) + { + Ogre::SubEntity* subEnt = ent->getSubEntity(i); + subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? RQG_Alpha : RQG_Main); + } ent->setRenderingDistance(small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0); ent->setVisibilityFlags(mIsStatic ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc); - ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); } } else @@ -192,28 +195,42 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) sg->setCastShadows(true); - sg->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + sg->setRenderQueueGroup(RQG_Main); - for(size_t i = 0;i < entities.mEntities.size();i++) + std::vector::reverse_iterator iter = entities.mEntities.rbegin(); + while(iter != entities.mEntities.rend()) { - Ogre::Entity *ent = entities.mEntities[i]; - insert->detachObject(ent); - sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); + Ogre::Node *node = (*iter)->getParentNode(); + sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); - mRenderer.getScene()->destroyEntity(ent); + (*iter)->detachFromParent(); + mRenderer.getScene()->destroyEntity(*iter); + iter++; } } + + if (light) + { + insertLight(ptr, entities.mSkelBase, bounds.getCenter() - insert->_getDerivedPosition()); + } } -void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, float radius) +void Objects::insertLight (const MWWorld::Ptr& ptr, Ogre::Entity* skelBase, Ogre::Vector3 fallbackCenter) { Ogre::SceneNode* insert = mRenderer.getScene()->getSceneNode(ptr.getRefData().getHandle()); assert(insert); - Ogre::Light *light = mRenderer.getScene()->createLight(); - light->setDiffuseColour (r, g, b); MWWorld::LiveCellRef *ref = ptr.get(); + const int color = ref->mBase->mData.mColor; + const float r = ((color >> 0) & 0xFF) / 255.0f; + const float g = ((color >> 8) & 0xFF) / 255.0f; + const float b = ((color >> 16) & 0xFF) / 255.0f; + const float radius = float (ref->mBase->mData.mRadius); + + Ogre::Light *light = mRenderer.getScene()->createLight(); + light->setDiffuseColour (r, g, b); + LightInfo info; info.name = light->getName(); info.radius = radius; @@ -235,20 +252,20 @@ void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, f else info.type = LT_Normal; - // random starting phase for the animation - info.time = Ogre::Math::RangeRandom(0, 2 * Ogre::Math::PI); + // randomize lights animations + info.time = Ogre::Math::RangeRandom(-500, +500); + info.phase = Ogre::Math::RangeRandom(-500, +500); - // adjust the lights depending if we're in an interior or exterior cell - // quadratic means the light intensity falls off quite fast, resulting in a - // dark, atmospheric environment (perfect for exteriors) - // for interiors, we want more "warm" lights, so use linear attenuation. + // changed to linear to look like morrowind bool quadratic = false; + /* if (!lightOutQuadInLin) quadratic = lightQuadratic; else { quadratic = !info.interior; } + */ if (!quadratic) { @@ -263,7 +280,17 @@ void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, f light->setAttenuation(r*10, 0, 0, attenuation); } - insert->attachObject(light); + // If there's an AttachLight bone, attach the light to that, otherwise attach it to the base scene node + if (skelBase && skelBase->getSkeleton ()->hasBone ("AttachLight")) + { + skelBase->attachObjectToBone ("AttachLight", light); + } + else + { + Ogre::SceneNode* childNode = insert->createChildSceneNode (fallbackCenter); + childNode->attachObject(light); + } + mLights.push_back(info); } @@ -348,9 +375,9 @@ void Objects::enableLights() std::vector::iterator it = mLights.begin(); while (it != mLights.end()) { - if (mMwRoot->getCreator()->hasLight(it->name)) + if (mRootNode->getCreator()->hasLight(it->name)) { - mMwRoot->getCreator()->getLight(it->name)->setVisible(true); + mRootNode->getCreator()->getLight(it->name)->setVisible(true); ++it; } else @@ -363,9 +390,9 @@ void Objects::disableLights() std::vector::iterator it = mLights.begin(); while (it != mLights.end()) { - if (mMwRoot->getCreator()->hasLight(it->name)) + if (mRootNode->getCreator()->hasLight(it->name)) { - mMwRoot->getCreator()->getLight(it->name)->setVisible(false); + mRootNode->getCreator()->getLight(it->name)->setVisible(false); ++it; } else @@ -373,86 +400,112 @@ void Objects::disableLights() } } +namespace MWRender +{ + namespace Pulse + { + static float amplitude (float phase) + { + return sin (phase); + } + } + + namespace Flicker + { + static const float fa = 0.785398f; + static const float fb = 1.17024f; + + static const float tdo = 0.94f; + static const float tdm = 2.48f; + + static const float f [3] = { 1.5708f, 4.18774f, 5.19934f }; + static const float o [3] = { 0.804248f, 2.11115f, 3.46832f }; + static const float m [3] = { 1.0f, 0.785f, 0.876f }; + static const float s = 0.394f; + + static const float phase_wavelength = 120.0f * 3.14159265359f / fa; + + static float frequency (float x) + { + return tdo + tdm * sin (fa * x); + } + + static float amplitude (float x) + { + float v = 0.0f; + for (int i = 0; i < 3; ++i) + v += sin (fb*x*f[i] + o[1])*m[i]; + return v * s; + } + } +} + void Objects::update(const float dt) { std::vector::iterator it = mLights.begin(); while (it != mLights.end()) { - if (mMwRoot->getCreator()->hasLight(it->name)) + if (mRootNode->getCreator()->hasLight(it->name)) { - Ogre::Light* light = mMwRoot->getCreator()->getLight(it->name); + Ogre::Light* light = mRootNode->getCreator()->getLight(it->name); - // Light animation (pulse & flicker) - it->time += dt; - const float phase = std::fmod(static_cast (it->time), static_cast(32 * 2 * Ogre::Math::PI)) * 20; - float pulseConstant; + float brightness; + float cycle_time; + float time_distortion; + + if ((it->type == LT_Pulse) && (it->type == LT_PulseSlow)) + { + cycle_time = 2 * Ogre::Math::PI; + time_distortion = 20.0f; + } + else + { + cycle_time = 500.0f; + it->phase = fmod (it->phase + dt, Flicker::phase_wavelength); + time_distortion = Flicker::frequency (it->phase); + } + + it->time += it->dir*dt*time_distortion; + if (it->dir > 0 && it->time > +cycle_time) + { + it->dir = -1.0f; + it->time = +2*cycle_time - it->time; + } + if (it->dir < 0 && it->time < -cycle_time) + { + it->dir = +1.0f; + it->time = -2*cycle_time - it->time; + } + + static const float fast = 4.0f/1.0f; + static const float slow = 1.0f/1.0f; // These formulas are just guesswork, but they work pretty well if (it->type == LT_Normal) { // Less than 1/255 light modifier for a constant light: - pulseConstant = (const float)(1.0 + sin(phase) / 255.0 ); + brightness = (const float)(1.0 + Flicker::amplitude(it->time*slow) / 255.0 ); } else if (it->type == LT_Flicker) { - // Let's do a 50% -> 100% sine wave pulse over 1 second: - // This is 75% +/- 25% - pulseConstant = (const float)(0.75 + sin(phase) * 0.25); - - // Then add a 25% flicker variation: - it->resetTime -= dt; - if (it->resetTime < 0) - { - it->flickerVariation = (rand() % 1000) / 1000 * 0.25; - it->resetTime = 0.5; - } - if (it->resetTime > 0.25) - { - pulseConstant = (pulseConstant+it->flickerVariation) * (1-it->resetTime * 2.0f) + pulseConstant * it->resetTime * 2.0f; - } - else - { - pulseConstant = (pulseConstant+it->flickerVariation) * (it->resetTime * 2.0f) + pulseConstant * (1-it->resetTime * 2.0f); - } + brightness = (const float)(0.75 + Flicker::amplitude(it->time*fast) * 0.25); } else if (it->type == LT_FlickerSlow) { - // Let's do a 50% -> 100% sine wave pulse over 1 second: - // This is 75% +/- 25% - pulseConstant = (const float)(0.75 + sin(phase / 4.0) * 0.25); - - // Then add a 25% flicker variation: - it->resetTime -= dt; - if (it->resetTime < 0) - { - it->flickerVariation = (rand() % 1000) / 1000 * 0.25; - it->resetTime = 0.5; - } - if (it->resetTime > 0.5) - { - pulseConstant = (pulseConstant+it->flickerVariation) * (1-it->resetTime) + pulseConstant * it->resetTime; - } - else - { - pulseConstant = (pulseConstant+it->flickerVariation) * (it->resetTime) + pulseConstant * (1-it->resetTime); - } + brightness = (const float)(0.75 + Flicker::amplitude(it->time*slow) * 0.25); } else if (it->type == LT_Pulse) { - // Let's do a 75% -> 125% sine wave pulse over 1 second: - // This is 100% +/- 25% - pulseConstant = (const float)(1.0 + sin(phase) * 0.25); + brightness = (const float)(1.0 + Pulse::amplitude (it->time*fast) * 0.25); } else if (it->type == LT_PulseSlow) { - // Let's do a 75% -> 125% sine wave pulse over 1 second: - // This is 100% +/- 25% - pulseConstant = (const float)(1.0 + sin(phase / 4.0) * 0.25); + brightness = (const float)(1.0 + Pulse::amplitude (it->time*slow) * 0.25); } else assert(0 && "Invalid light type"); - light->setDiffuseColour( it->colour * pulseConstant ); + light->setDiffuseColour(it->colour * brightness); ++it; } @@ -476,22 +529,17 @@ void Objects::rebuildStaticGeometry() } } -void -Objects::updateObjectCell(const MWWorld::Ptr &ptr) +void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { Ogre::SceneNode *node; - MWWorld::CellStore *newCell = ptr.getCell(); + MWWorld::CellStore *newCell = cur.getCell(); if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) { - node = mMwRoot->createChildSceneNode(); + node = mRootNode->createChildSceneNode(); mCellSceneNodes[newCell] = node; } else { node = mCellSceneNodes[newCell]; } - node->addChild(ptr.getRefData().getBaseNode()); - - /// \note Still unaware how to move aabb and static w/o full rebuild, - /// moving static objects may cause problems - insertMesh(ptr, MWWorld::Class::get(ptr).getModel(ptr)); + node->addChild(cur.getRefData().getBaseNode()); } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 8594e4fe4..73e95a3c5 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -2,6 +2,7 @@ #define _GAME_RENDER_OBJECTS_H #include +#include #include @@ -34,16 +35,13 @@ struct LightInfo LightType type; // Runtime variables - float flickerVariation; // 25% flicker variation, reset once every 0.5 seconds - float flickerSlowVariation; // 25% flicker variation, reset once every 1.0 seconds - float resetTime; - long double time; - + float dir; // direction time is running... + float time; // current time + float phase; // current phase LightInfo() : - flickerVariation(0), resetTime(0.5), - flickerSlowVariation(0), time(0), interior(true), - type(LT_Normal), radius(1.0) + dir(1.0f), time(0.0f), phase (0.0f), + interior(true), type(LT_Normal), radius(1.0) { } }; @@ -55,7 +53,7 @@ class Objects{ std::map mStaticGeometrySmall; std::map mBounds; std::vector mLights; - Ogre::SceneNode* mMwRoot; + Ogre::SceneNode* mRootNode; bool mIsStatic; static int uniqueID; @@ -75,8 +73,8 @@ public: Objects(OEngine::Render::OgreRenderer& renderer): mRenderer (renderer), mIsStatic(false) {} ~Objects(){} void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_); - void insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh); - void insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, float radius); + void insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh, bool light=false); + void insertLight (const MWWorld::Ptr& ptr, Ogre::Entity *skelBase=0, Ogre::Vector3 fallbackCenter=Ogre::Vector3(0.0f)); void enableLights(); void disableLights(); @@ -92,12 +90,12 @@ public: void removeCell(MWWorld::CellStore* store); void buildStaticGeometry(MWWorld::CellStore &cell); - void setMwRoot(Ogre::SceneNode* root); + void setRootNode(Ogre::SceneNode* root); void rebuildStaticGeometry(); /// Updates containing cell for object rendering data - void updateObjectCell(const MWWorld::Ptr &ptr); + void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); }; } #endif diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 6d3f67de9..81a3f4327 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -15,8 +15,8 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false), - mBBNode(0) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), + mBBNode(0), mActive(false) { mRendering = renderer; mSunNode = sunNode; @@ -94,6 +94,9 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod OcclusionQuery::~OcclusionQuery() { + mRendering->getScene()->removeRenderObjectListener (this); + mRendering->getScene()->removeRenderQueueListener(this); + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); @@ -108,8 +111,10 @@ bool OcclusionQuery::supported() void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, const LightList* pLightList, bool suppressRenderStateChanges) { + if (!mActive) return; + // The following code activates and deactivates the occlusion queries - // so that the queries only include the rendering of their intended targets + // so that the queries only include the rendering of the intended meshes // Close the last occlusion query // Each occlusion query should only last a single rendering @@ -146,6 +151,8 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) { + if (!mActive) return; + if (mActiveQuery != NULL) { mActiveQuery->endOcclusionQuery(); @@ -247,6 +254,8 @@ void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNod mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); mQuerySingleObjectRequested = true; + + mDoQuery = true; } bool OcclusionQuery::occlusionTestPending() diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index c76fcccd0..c7a5757d3 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -29,6 +29,12 @@ namespace MWRender */ bool supported(); + /** + * make sure to disable occlusion queries before updating unrelated render targets + * @param active + */ + void setActive (bool active) { mActive = active; } + /** * per-frame update */ @@ -85,9 +91,10 @@ namespace MWRender bool mTestResult; + bool mActive; + bool mSupported; bool mDoQuery; - bool mDoQuery2; bool mQuerySingleObjectRequested; bool mQuerySingleObjectStarted; diff --git a/apps/openmw/mwrender/player.cpp b/apps/openmw/mwrender/player.cpp index 0e4c76b0e..63396378d 100644 --- a/apps/openmw/mwrender/player.cpp +++ b/apps/openmw/mwrender/player.cpp @@ -113,11 +113,6 @@ namespace MWRender Ogre::Vector3 dir = mCamera->getRealDirection(); Ogre::Vector3 up = mCamera->getRealUp(); - Ogre::Real xch; - xch = pos.y, pos.y = -pos.z, pos.z = xch; - xch = dir.y, dir.y = -dir.z, dir.z = xch; - xch = up.y, up.y = -up.z, up.z = xch; - MWBase::Environment::get().getSoundManager()->setListenerPosDir(pos, dir, up); } @@ -129,14 +124,8 @@ namespace MWRender MWBase::Environment::get().getWindowManager ()->showCrosshair (!MWBase::Environment::get().getWindowManager ()->isGuiMode () && (mFirstPersonView && !mVanity.enabled && !mPreviewMode)); - if (mAnimation) { - mAnimation->runAnimation(duration); - } - mPlayerNode->setVisible( - mVanity.enabled || mPreviewMode || !mFirstPersonView, - false - ); - + /// \fixme We shouldn't hide the whole model, just certain components of the character (head, chest, feet, etc) + mPlayerNode->setVisible(mVanity.enabled || mPreviewMode || !mFirstPersonView); if (mFirstPersonView && !mVanity.enabled) { return; } @@ -282,6 +271,8 @@ namespace MWRender v.z = 800.f; } else if (v.z < 10.f) { v.z = 10.f; + } else if (override && v.z < 50.f) { + v.z = 50.f; } mCamera->setPosition(v); @@ -313,10 +304,7 @@ namespace MWRender delete mAnimation; mAnimation = anim; - mPlayerNode->setVisible( - mVanity.enabled || mPreviewMode || !mFirstPersonView, - false - ); + mPlayerNode->setVisible(mVanity.enabled || mPreviewMode || !mFirstPersonView); } void Player::setHeight(float height) @@ -332,10 +320,8 @@ namespace MWRender bool Player::getPosition(Ogre::Vector3 &player, Ogre::Vector3 &camera) { - float xch; mCamera->getParentSceneNode ()->needUpdate(true); camera = mCamera->getRealPosition(); - xch = camera.z, camera.z = camera.y, camera.y = -xch; player = mPlayerNode->getPosition(); return mFirstPersonView && !mVanity.enabled && !mPreviewMode; @@ -378,4 +364,9 @@ namespace MWRender mCameraNode->setPosition(0.f, 0.f, mHeight); } } + + bool Player::isVanityOrPreviewModeEnabled() + { + return mPreviewMode || mVanity.enabled; + } } diff --git a/apps/openmw/mwrender/player.hpp b/apps/openmw/mwrender/player.hpp index 29a68ca69..9de41823d 100644 --- a/apps/openmw/mwrender/player.hpp +++ b/apps/openmw/mwrender/player.hpp @@ -95,7 +95,9 @@ namespace MWRender /// Restore default camera distance for current mode. void setCameraDistance(); - void setAnimation(MWRender::NpcAnimation *anim); + void setAnimation(NpcAnimation *anim); + NpcAnimation *getAnimation() const + { return mAnimation; } void setHeight(float height); float getHeight(); @@ -108,6 +110,8 @@ namespace MWRender void getSightAngles(float &pitch, float &yaw); void togglePlayerLooking(bool enable); + + bool isVanityOrPreviewModeEnabled(); }; } diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp new file mode 100644 index 000000000..d590dbf4c --- /dev/null +++ b/apps/openmw/mwrender/refraction.cpp @@ -0,0 +1,107 @@ +#include "refraction.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "renderconst.hpp" + +namespace MWRender +{ + + Refraction::Refraction(Ogre::Camera *parentCamera) + : mParentCamera(parentCamera) + , mRenderActive(false) + , mIsUnderwater(false) + { + mCamera = mParentCamera->getSceneManager()->createCamera("RefractionCamera"); + + mParentCamera->getSceneManager()->addRenderQueueListener(this); + + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual("WaterRefraction", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, 512, 512, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + + mRenderTarget = texture->getBuffer()->getRenderTarget(); + Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky); + vp->setMaterialScheme("water_refraction"); + vp->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + mRenderTarget->setAutoUpdated(true); + mRenderTarget->addListener(this); + } + + Refraction::~Refraction() + { + mRenderTarget->removeListener(this); + Ogre::TextureManager::getSingleton().remove("WaterRefraction"); + mParentCamera->getSceneManager()->destroyCamera(mCamera); + mParentCamera->getSceneManager()->removeRenderQueueListener(this); + } + + void Refraction::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) + { + mParentCamera->getParentSceneNode ()->needUpdate (); + mCamera->setOrientation(mParentCamera->getDerivedOrientation()); + mCamera->setPosition(mParentCamera->getDerivedPosition()); + mCamera->setNearClipDistance(mParentCamera->getNearClipDistance()); + mCamera->setFarClipDistance(mParentCamera->getFarClipDistance()); + mCamera->setAspectRatio(mParentCamera->getAspectRatio()); + mCamera->setFOVy(mParentCamera->getFOVy()); + + // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane. + // since all we are interested in is depth, we only need the third row of the matrix. + Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix (); + sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]); + sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (row3)); + + // enable clip plane here to take advantage of CPU culling for overwater or underwater objects + mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane); + + mRenderActive = true; + } + + void Refraction::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) + { + mCamera->disableCustomNearClipPlane (); + mRenderActive = false; + } + + void Refraction::setHeight(float height) + { + mNearClipPlane = Ogre::Plane( -Ogre::Vector3(0,0,1), -(height + 5)); + mNearClipPlaneUnderwater = Ogre::Plane( Ogre::Vector3(0,0,1), height - 5); + } + + void Refraction::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) + { + // We don't want the sky to get clipped by custom near clip plane (the water plane) + if (queueGroupId < 20 && mRenderActive) + { + mCamera->disableCustomNearClipPlane(); + Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + } + } + + void Refraction::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) + { + if (queueGroupId < 20 && mRenderActive) + { + mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane); + Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + } + } + + void Refraction::setActive(bool active) + { + mRenderTarget->setActive(active); + } + +} diff --git a/apps/openmw/mwrender/refraction.hpp b/apps/openmw/mwrender/refraction.hpp new file mode 100644 index 000000000..b9ab8deac --- /dev/null +++ b/apps/openmw/mwrender/refraction.hpp @@ -0,0 +1,45 @@ +#ifndef MWRENDER_REFRACTION_H +#define MWRENDER_REFRACTION_H + +#include +#include +#include + +namespace Ogre +{ + class Camera; + class RenderTarget; +} + +namespace MWRender +{ + + class Refraction : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener + { + + public: + Refraction(Ogre::Camera* parentCamera); + ~Refraction(); + + void setHeight (float height); + void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void setUnderwater(bool underwater) {mIsUnderwater = underwater;} + void setActive (bool active); + + void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); + void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); + + private: + Ogre::Camera* mParentCamera; + Ogre::Camera* mCamera; + Ogre::RenderTarget* mRenderTarget; + Ogre::Plane mNearClipPlane; + Ogre::Plane mNearClipPlaneUnderwater; + bool mRenderActive; + bool mIsUnderwater; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index 75e243ec7..1d2cdf1ea 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -20,7 +20,7 @@ enum RenderQueueGroups RQG_UnderWater = Ogre::RENDER_QUEUE_4, - RQG_Water = Ogre::RENDER_QUEUE_7+1, + RQG_Water = RQG_Alpha, // Sky late (sun & sun flare) RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE @@ -54,9 +54,10 @@ enum VisibilityFlags RV_OcclusionQuery = 256, - RV_PlayerPreview = 512, + RV_Debug = 512, - RV_Debug = 1024, + // overlays, we only want these on the main render target + RV_Overlay = 1024, RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8cb8e9fa8..e6216b537 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -18,9 +18,12 @@ #include #include +#include + #include -#include "../mwworld/esmstore.hpp" #include +#include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" @@ -37,6 +40,7 @@ #include "npcanimation.hpp" #include "externalrendering.hpp" #include "globalmap.hpp" +#include "videoplayer.hpp" using namespace MWRender; using namespace Ogre; @@ -45,7 +49,12 @@ namespace MWRender { RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, OEngine::Physic::PhysicEngine* engine) - : mRendering(_rend), mObjects(mRendering), mActors(mRendering), mAmbientMode(0), mSunEnabled(0), mPhysicsEngine(engine) + : mRendering(_rend) + , mObjects(mRendering) + , mActors(mRendering, this) + , mAmbientMode(0) + , mSunEnabled(0) + , mPhysicsEngine(engine) { // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); @@ -61,6 +70,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); mRendering.setWindowEventListener(this); + mRendering.getWindow()->addListener(this); + mCompositors = new Compositors(mRendering.getViewport()); mWater = 0; @@ -89,7 +100,9 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mFactory->loadAllFiles(); // Set default mipmap level (NB some APIs ignore this) - TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); + // Mipmap generation is currently disabled because it causes issues on Intel/AMD + //TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); + TextureManager::getSingleton().setDefaultNumMipmaps(0); // Set default texture filtering options TextureFilterOptions tfo; @@ -110,66 +123,61 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const //mRendering.getScene()->setCameraRelativeRendering(true); // disable unsupported effects - //const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); - if (!waterShaderSupported()) - Settings::Manager::setBool("shader", "Water", false); if (!Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); - sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); - sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); sh::Factory::getInstance ().setGlobalSetting ("terrain_num_lights", Settings::Manager::getString ("num lights", "Terrain")); - sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); + sh::Factory::getInstance ().setGlobalSetting ("render_refraction", "false"); - sh::Factory::getInstance ().setSharedParameter ("viewportBackground", sh::makeProperty (new sh::Vector3(0,0,0))); sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(0.0))); sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(0))); sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5, -0.8, 0.2))); sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); - sh::Factory::getInstance ().setSharedParameter ("gammaCorrection", sh::makeProperty(new sh::FloatValue( - Settings::Manager::getFloat ("gamma", "Video")))); + sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false"); + sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); + sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (new sh::Vector4(0,0,0,0))); applyCompositors(); - // Turn the entire scene (represented by the 'root' node) -90 - // degrees around the x axis. This makes Z go upwards, and Y go into - // the screen (when x is to the right.) This is the orientation that - // Morrowind uses, and it automagically makes everything work as it - // should. SceneNode *rt = mRendering.getScene()->getRootSceneNode(); - mMwRoot = rt->createChildSceneNode("mwRoot"); - mMwRoot->pitch(Degree(-90)); + mRootNode = rt; - mObjects.setMwRoot(mMwRoot); - mActors.setMwRoot(mMwRoot); + mObjects.setRootNode(mRootNode); + mActors.setRootNode(mRootNode); - Ogre::SceneNode *playerNode = mMwRoot->createChildSceneNode ("player"); + Ogre::SceneNode *playerNode = mRootNode->createChildSceneNode ("player"); mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mShadows = new Shadows(&mRendering); mTerrainManager = new TerrainManager(mRendering.getScene(), this); - mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera()); + mSkyManager = new SkyManager(mRootNode, mRendering.getCamera()); mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mVideoPlayer = new VideoPlayer(mRendering.getScene ()); + mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video")); + mSun = 0; - mDebugging = new Debugging(mMwRoot, engine); + mDebugging = new Debugging(mRootNode, engine); mLocalMap = new MWRender::LocalMap(&mRendering, this); + mWater = new MWRender::Water(mRendering.getCamera(), this); + setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } RenderingManager::~RenderingManager () { + mRendering.getWindow()->removeListener(this); mRendering.removeWindowEventListener(this); delete mPlayer; @@ -181,7 +189,7 @@ RenderingManager::~RenderingManager () delete mOcclusionQuery; delete mCompositors; delete mWater; - + delete mVideoPlayer; delete mFactory; } @@ -213,15 +221,12 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) void RenderingManager::removeWater () { - if(mWater){ - mWater->setActive(false); - } + mWater->setActive(false); } void RenderingManager::toggleWater() { - if (mWater) - mWater->toggle(); + mWater->toggle(); } void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) @@ -248,8 +253,7 @@ void RenderingManager::removeObject (const MWWorld::Ptr& ptr) void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position) { /// \todo move this to the rendering-subsystems - mRendering.getScene()->getSceneNode (ptr.getRefData().getHandle())-> - setPosition (position); + ptr.getRefData().getBaseNode()->setPosition(position); } void RenderingManager::scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale) @@ -270,57 +274,68 @@ bool RenderingManager::rotateObject( const MWWorld::Ptr &ptr, Ogre::Vector3 &rot if (!isPlayer && isActive) { - Ogre::Quaternion xr(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X); - Ogre::Quaternion yr(Ogre::Radian(rot.y), Ogre::Vector3::UNIT_Y); - Ogre::Quaternion zr(Ogre::Radian(rot.z), Ogre::Vector3::UNIT_Z); - Ogre::Quaternion newo = adjust ? (xr * yr * zr) * ptr.getRefData().getBaseNode()->getOrientation() : xr * yr * zr; - rot.x = newo.x; - rot.y = newo.y; - rot.z = newo.z; + Ogre::Quaternion xr(Ogre::Radian(-rot.x), Ogre::Vector3::UNIT_X); + Ogre::Quaternion yr(Ogre::Radian(-rot.y), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion zr(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); + + Ogre::Quaternion xref(Ogre::Radian(-ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion yref(Ogre::Radian(-ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion zref(Ogre::Radian(-ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::UNIT_Z); + + Ogre::Quaternion newo = adjust ? (xr * yr * zr) * (xref*yref*zref) : xr * yr * zr; + + Ogre::Matrix3 mat; + newo.ToRotationMatrix(mat); + Ogre::Radian ax,ay,az; + mat.ToEulerAnglesXYZ(ax,ay,az); + rot.x = -ax.valueRadians(); + rot.y = -ay.valueRadians(); + rot.z = -az.valueRadians(); + ptr.getRefData().getBaseNode()->setOrientation(newo); } else if(isPlayer) { - rot.x = mPlayer->getPitch(); + rot.x = -mPlayer->getPitch(); rot.z = mPlayer->getYaw(); } - else if (adjust) - { - // Stored and passed in radians - float *f = ptr.getRefData().getPosition().rot; - rot.x += f[0]; - rot.y += f[1]; - rot.z += f[2]; - } return force; } void -RenderingManager::moveObjectToCell( - const MWWorld::Ptr& ptr, - const Ogre::Vector3& pos, - MWWorld::CellStore *store) +RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { Ogre::SceneNode *child = - mRendering.getScene()->getSceneNode(ptr.getRefData().getHandle()); + mRendering.getScene()->getSceneNode(old.getRefData().getHandle()); Ogre::SceneNode *parent = child->getParentSceneNode(); parent->removeChild(child); - if (MWWorld::Class::get(ptr).isActor()) { - mActors.updateObjectCell(ptr); + if (MWWorld::Class::get(old).isActor()) { + mActors.updateObjectCell(old, cur); } else { - mObjects.updateObjectCell(ptr); + mObjects.updateObjectCell(old, cur); } - child->setPosition(pos); } void RenderingManager::update (float duration, bool paused) { + MWBase::World *world = MWBase::Environment::get().getWorld(); + + // player position + MWWorld::RefData &data = + MWBase::Environment::get() + .getWorld() + ->getPlayer() + .getPlayer() + .getRefData(); + float *_playerPos = data.getPosition().pos; + Ogre::Vector3 playerPos(_playerPos[0], _playerPos[1], _playerPos[2]); + Ogre::Vector3 orig, dest; mPlayer->setCameraDistance(); if (!mPlayer->getPosition(orig, dest)) { - orig.z += mPlayer->getHeight() * mMwRoot->getScale().z; + orig.z += mPlayer->getHeight() * mRootNode->getScale().z; btVector3 btOrig(orig.x, orig.y, orig.z); btVector3 btDest(dest.x, dest.y, dest.z); @@ -330,17 +345,23 @@ void RenderingManager::update (float duration, bool paused) mPlayer->setCameraDistance(test.second * orig.distance(dest), false, false); } } + mOcclusionQuery->update(duration); - + + mVideoPlayer->update (); + mRendering.update(duration); + Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f); + + Ogre::Vector3 cam = mRendering.getCamera()->getRealPosition(); + + applyFog(world->isUnderwater (world->getPlayer().getPlayer().getCell(), cam)); + if(paused) { - Ogre::ControllerManager::getSingleton().setTimeFactor(0.f); return; } - Ogre::ControllerManager::getSingleton().setTimeFactor( - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); mPlayer->update(duration); @@ -352,36 +373,33 @@ void RenderingManager::update (float duration, bool paused) mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); - MWWorld::RefData &data = - MWBase::Environment::get() - .getWorld() - ->getPlayer() - .getPlayer() - .getRefData(); - - float *fpos = data.getPosition().pos; - - // only for LocalMap::updatePlayer() - Ogre::Vector3 pos(fpos[0], -fpos[2], -fpos[1]); - Ogre::SceneNode *node = data.getBaseNode(); + //Ogre::Quaternion orient = + //node->convertLocalToWorldOrientation(node->_getDerivedOrientation()); Ogre::Quaternion orient = - node->convertLocalToWorldOrientation(node->_getDerivedOrientation()); +node->_getDerivedOrientation(); - mLocalMap->updatePlayer(pos, orient); + mLocalMap->updatePlayer(playerPos, orient); - if (mWater) { - Ogre::Vector3 cam = mRendering.getCamera()->getRealPosition(); + mWater->updateUnderwater( + world->isUnderwater( + world->getPlayer().getPlayer().getCell(), + cam) + ); - MWBase::World *world = MWBase::Environment::get().getWorld(); + mWater->update(duration, playerPos); +} - mWater->updateUnderwater( - world->isUnderwater( - *world->getPlayer().getPlayer().getCell()->mCell, - Ogre::Vector3(cam.x, -cam.z, cam.y)) - ); - mWater->update(duration); - } +void RenderingManager::preRenderTargetUpdate(const RenderTargetEvent &evt) +{ + mOcclusionQuery->setActive(true); +} + +void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt) +{ + // deactivate queries to make sure we aren't getting false results from several misc render targets + // (will be reactivated at the bottom of this method) + mOcclusionQuery->setActive(false); } void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store) @@ -393,10 +411,7 @@ void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store) || ((store->mCell->isExterior()) && !lands.search(store->mCell->getGridX(),store->mCell->getGridY()) )) // always use water, if the cell does not have land. { - if(mWater == 0) - mWater = new MWRender::Water(mRendering.getCamera(), this, store->mCell); - else - mWater->changeCell(store->mCell); + mWater->changeCell(store->mCell); mWater->setActive(true); } else @@ -405,8 +420,7 @@ void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store) void RenderingManager::setWaterHeight(const float height) { - if (mWater) - mWater->setHeight(height); + mWater->setHeight(height); } void RenderingManager::skyEnable () @@ -491,31 +505,34 @@ void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell) color.setAsABGR (mCell.mCell->mAmbi.mFog); configureFog(mCell.mCell->mAmbi.mFogDensity, color); - - if (mWater) - mWater->setViewportBackground (Ogre::ColourValue(0.8f, 0.9f, 1.0f)); } void RenderingManager::configureFog(const float density, const Ogre::ColourValue& colour) { + mFogColour = colour; float max = Settings::Manager::getFloat("max viewing distance", "Viewing distance"); - float low = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); - float high = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); - - mRendering.getScene()->setFog (FOG_LINEAR, colour, 0, low, high); - - mRendering.getCamera()->setFarClipDistance ( max / density ); - mRendering.getViewport()->setBackgroundColour (colour); - - if (mWater) - mWater->setViewportBackground (colour); - - sh::Factory::getInstance ().setSharedParameter ("viewportBackground", - sh::makeProperty (new sh::Vector3(colour.r, colour.g, colour.b))); + mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); + mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); + mRendering.getCamera()->setFarClipDistance ( Settings::Manager::getFloat("max viewing distance", "Viewing distance") / density ); } +void RenderingManager::applyFog (bool underwater) +{ + if (!underwater) + { + mRendering.getScene()->setFog (FOG_LINEAR, mFogColour, 0, mFogStart, mFogEnd); + mRendering.getViewport()->setBackgroundColour (mFogColour); + mWater->setViewportBackground (mFogColour); + } + else + { + mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(0.18039, 0.23137, 0.25490), 0, 0, 1000); + mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + mWater->setViewportBackground (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + } +} void RenderingManager::setAmbientMode() { @@ -574,17 +591,6 @@ void RenderingManager::toggleLight() setAmbientMode(); } -void RenderingManager::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, - int mode, int number) -{ - mActors.playAnimationGroup(ptr, groupName, mode, number); -} - -void RenderingManager::skipAnimation (const MWWorld::Ptr& ptr) -{ - mActors.skipAnimation(ptr); -} - void RenderingManager::setSunColour(const Ogre::ColourValue& colour) { if (!mSunEnabled) return; @@ -599,30 +605,35 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) mTerrainManager->setAmbient(colour); } -void RenderingManager::sunEnable() +void RenderingManager::sunEnable(bool real) { - // Don't disable the light, as the shaders assume the first light to be directional. - //if (mSun) mSun->setVisible(true); - mSunEnabled = true; + if (real && mSun) mSun->setVisible(true); + else + { + // Don't disable the light, as the shaders assume the first light to be directional. + mSunEnabled = true; + } } -void RenderingManager::sunDisable() +void RenderingManager::sunDisable(bool real) { - // Don't disable the light, as the shaders assume the first light to be directional. - //if (mSun) mSun->setVisible(false); - mSunEnabled = false; - if (mSun) + if (real && mSun) mSun->setVisible(false); + else { - mSun->setDiffuseColour(ColourValue(0,0,0)); - mSun->setSpecularColour(ColourValue(0,0,0)); + // Don't disable the light, as the shaders assume the first light to be directional. + mSunEnabled = false; + if (mSun) + { + mSun->setDiffuseColour(ColourValue(0,0,0)); + mSun->setSpecularColour(ColourValue(0,0,0)); + } } } void RenderingManager::setSunDirection(const Ogre::Vector3& direction) { // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), - // then convert from MW to ogre coordinates (swap y,z and make y negative) - if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.z, direction.y)); + if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z)); mSkyManager->setSunDirection(direction); } @@ -645,21 +656,16 @@ void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) mLocalMap->saveFogOfWar(cell); } -void RenderingManager::disableLights() +void RenderingManager::disableLights(bool sun) { mObjects.disableLights(); - sunDisable(); + sunDisable(sun); } -void RenderingManager::enableLights() +void RenderingManager::enableLights(bool sun) { mObjects.enableLights(); - sunEnable(); -} - -const bool RenderingManager::useMRT() -{ - return Settings::Manager::getBool("shader", "Water"); + sunEnable(sun); } Shadows* RenderingManager::getShadows() @@ -726,6 +732,7 @@ Compositors* RenderingManager::getCompositors() void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) { bool changeRes = false; + bool rebuild = false; // rebuild static geometry (necessary after any material changes) for (Settings::CategorySettingVector::const_iterator it=settings.begin(); it != settings.end(); ++it) { @@ -761,25 +768,19 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec else if (it->second == "shader" && it->first == "Water") { applyCompositors(); - sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); - mObjects.rebuildStaticGeometry (); + rebuild = true; mRendering.getViewport ()->setClearEveryFrame (true); } - else if (it->second == "underwater effect" && it->first == "Water") + else if (it->second == "refraction" && it->first == "Water") { - sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); - mObjects.rebuildStaticGeometry (); + sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false"); + rebuild = true; } else if (it->second == "shaders" && it->first == "Objects") { sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); - mObjects.rebuildStaticGeometry (); - } - else if (it->second == "gamma" && it->first == "Video") - { - sh::Factory::getInstance ().setSharedParameter ("gammaCorrection", sh::makeProperty(new sh::FloatValue( - Settings::Manager::getFloat ("gamma", "Video")))); + rebuild = true; } else if (it->second == "shader mode" && it->first == "General") { @@ -792,13 +793,13 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec else lang = sh::Language_CG; sh::Factory::getInstance ().setCurrentLanguage (lang); - mObjects.rebuildStaticGeometry (); + rebuild = true; } else if (it->first == "Shadows") { mShadows->recreate (); - mObjects.rebuildStaticGeometry (); + rebuild = true; } } @@ -814,8 +815,10 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec mRendering.getWindow()->setFullscreen(Settings::Manager::getBool("fullscreen", "Video"), x, y); } - if (mWater) - mWater->processChangedSettings(settings); + mWater->processChangedSettings(settings); + + if (rebuild) + mObjects.rebuildStaticGeometry(); } void RenderingManager::setMenuTransparency(float val) @@ -836,11 +839,12 @@ void RenderingManager::windowResized(Ogre::RenderWindow* rw) mRendering.adjustViewport(); mCompositors->recreate(); - mWater->assignTextures(); + + mVideoPlayer->setResolution (rw->getWidth(), rw->getHeight()); const Settings::CategorySettingVector& changed = Settings::Manager::apply(); - MWBase::Environment::get().getInputManager()->processChangedSettings(changed); //FIXME - MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); // FIXME + MWBase::Environment::get().getInputManager()->processChangedSettings(changed); + MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); } void RenderingManager::windowClosed(Ogre::RenderWindow* rw) @@ -848,27 +852,8 @@ void RenderingManager::windowClosed(Ogre::RenderWindow* rw) Ogre::Root::getSingleton ().queueEndRendering (); } -bool RenderingManager::waterShaderSupported() -{ - const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); - if (caps->getNumMultiRenderTargets() < 2 || !Settings::Manager::getBool("shaders", "Objects")) - return false; - return true; -} - void RenderingManager::applyCompositors() { - mCompositors->removeAll(); - if (useMRT()) - { - mCompositors->addCompositor("gbuffer", 0); - mCompositors->setCompositorEnabled("gbuffer", true); - mCompositors->addCompositor("gbufferFinalizer", 2); - mCompositors->setCompositorEnabled("gbufferFinalizer", true); - } - - if (mWater) - mWater->assignTextures(); } void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) @@ -897,6 +882,8 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr) MWWorld::Class::get(ptr).getInventoryStore(ptr), RV_Actors ); mPlayer->setAnimation(anim); + mWater->removeEmitter (ptr); + mWater->addEmitter (ptr); } void RenderingManager::getPlayerData(Ogre::Vector3 &eyepos, float &pitch, float &yaw) @@ -921,4 +908,43 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend rendering.setup (mRendering.getScene()); } +Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) +{ + Animation *anim = mActors.getAnimation(ptr); + if(!anim && ptr.getRefData().getHandle() == "player") + anim = mPlayer->getAnimation(); + return anim; +} + + +void RenderingManager::playVideo(const std::string& name, bool allowSkipping) +{ + mVideoPlayer->playVideo ("video/" + name, allowSkipping); +} + +void RenderingManager::stopVideo() +{ + mVideoPlayer->stopVideo (); +} + +void RenderingManager::addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale, float force) +{ + mWater->addEmitter (ptr, scale, force); +} + +void RenderingManager::removeWaterRippleEmitter (const MWWorld::Ptr& ptr) +{ + mWater->removeEmitter (ptr); +} + +void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +{ + mWater->updateEmitterPtr(old, ptr); +} + +void RenderingManager::frameStarted(float dt) +{ + mWater->frameStarted(dt); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 86346d1d6..1777a72c3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -11,6 +11,8 @@ #include +#include + #include "renderinginterface.hpp" #include "objects.hpp" @@ -45,8 +47,10 @@ namespace MWRender class Compositors; class ExternalRendering; class GlobalMap; + class VideoPlayer; + class Animation; -class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener { +class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener, public Ogre::RenderTargetListener { private: @@ -79,6 +83,11 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList mPlayer->togglePlayerLooking(enable); } + void changeVanityModeScale(float factor) { + if (mPlayer->isVanityOrPreviewModeEnabled()) + mPlayer->setCameraDistance(-factor/120.f*10, true, true); + } + void getPlayerData(Ogre::Vector3 &eyepos, float &pitch, float &yaw); void attachCameraTo(const MWWorld::Ptr &ptr); @@ -101,8 +110,6 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void removeWater(); - static const bool useMRT(); - void preCellChange (MWWorld::CellStore* store); ///< this event is fired immediately before changing cell @@ -121,20 +128,25 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void setWaterHeight(const float height); void toggleWater(); - /// Moves object rendering part to proper container - /// \param store Cell the object was in previously (\a ptr has already been updated to the new cell). - void moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Vector3& position, MWWorld::CellStore *store); + /// Updates object rendering after cell change + /// \param old Object reference in previous cell + /// \param cur Object reference in new cell + void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); void update (float duration, bool paused); void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); void setSunDirection(const Ogre::Vector3& direction); - void sunEnable(); - void sunDisable(); + void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0) + void sunDisable(bool real); - void disableLights(); - void enableLights(); + void disableLights(bool sun); ///< @param sun whether or not to really disable the sunlight (otherwise just set diffuse to 0) + void enableLights(bool sun); + + + void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); bool occlusionQuerySupported() { return mOcclusionQuery->supported(); } OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; } @@ -156,6 +168,10 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void skySetMoonColour (bool red); void configureAmbient(MWWorld::CellStore &mCell); + void addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); + void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); + void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void requestMap (MWWorld::CellStore* cell); ///< request the local map for a cell @@ -165,18 +181,6 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList /// configure fog manually void configureFog(const float density, const Ogre::ColourValue& colour); - void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, - int number = 1); - ///< Run animation for a MW-reference. Calls to this function for references that are currently not - /// in the rendered scene should be ignored. - /// - /// \param mode: 0 normal, 1 immediate start, 2 immediate loop - /// \param number How offen the animation should be run - - void skipAnimation (const MWWorld::Ptr& ptr); - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the rendered scene should be ignored. - Ogre::Vector4 boundingBoxToScreen(Ogre::AxisAlignedBox bounds); ///< transform the specified bounding box (in world coordinates) into screen coordinates. /// @return packed vector4 (min_x, min_y, max_x, max_y) @@ -185,8 +189,6 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList Ogre::Viewport* getViewport() { return mRendering.getViewport(); } - static bool waterShaderSupported(); - void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); ///< see MWRender::LocalMap::getInteriorMapPosition @@ -195,6 +197,12 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void setupExternalRendering (MWRender::ExternalRendering& rendering); + Animation* getAnimation(const MWWorld::Ptr &ptr); + + void playVideo(const std::string& name, bool allowSkipping); + void stopVideo(); + void frameStarted(float dt); + protected: virtual void windowResized(Ogre::RenderWindow* rw); virtual void windowClosed(Ogre::RenderWindow* rw); @@ -204,6 +212,7 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList sh::Factory* mFactory; void setAmbientMode(); + void applyFog(bool underwater); void setMenuTransparency(float val); @@ -232,10 +241,11 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList Ogre::ColourValue mAmbientColor; Ogre::Light* mSun; - /// Root node for all objects added to the scene. This is rotated so - /// that the OGRE coordinate system matches that used internally in - /// Morrowind. - Ogre::SceneNode *mMwRoot; + Ogre::SceneNode *mRootNode; + + Ogre::ColourValue mFogColour; + float mFogStart; + float mFogEnd; OEngine::Physic::PhysicEngine* mPhysicsEngine; @@ -248,6 +258,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList MWRender::Shadows* mShadows; MWRender::Compositors* mCompositors; + + VideoPlayer* mVideoPlayer; }; } diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp new file mode 100644 index 000000000..47fbc8ddf --- /dev/null +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -0,0 +1,263 @@ +#include "ripplesimulation.hpp" + +#include +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/player.hpp" + +namespace MWRender +{ + + +RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager) + : mMainSceneMgr(mainSceneManager), + mTime(0), + mCurrentFrameOffset(0,0), + mPreviousFrameOffset(0,0), + mRippleCenter(0,0), + mTextureSize(512), + mRippleAreaLength(1000), + mImpulseSize(20), + mTexelOffset(0,0), + mFirstUpdate(true) +{ + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + mCamera = mSceneMgr->createCamera("RippleCamera"); + + mRectangle = new Ogre::Rectangle2D(true); + mRectangle->setBoundingBox(aabInf); + mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0, false); + Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + node->attachObject(mRectangle); + + mImpulse = new Ogre::Rectangle2D(true); + mImpulse->setCorners(-0.1, 0.1, 0.1, -0.1, false); + mImpulse->setBoundingBox(aabInf); + mImpulse->setMaterial("AddImpulse"); + Ogre::SceneNode* impulseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + impulseNode->attachObject(mImpulse); + + //float w=0.05; + for (int i=0; i<4; ++i) + { + Ogre::TexturePtr texture; + if (i != 3) + texture = Ogre::TextureManager::getSingleton().createManual("RippleHeight" + Ogre::StringConverter::toString(i), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + else + texture = Ogre::TextureManager::getSingleton().createManual("RippleNormal", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + + + Ogre::RenderTexture* rt = texture->getBuffer()->getRenderTarget(); + rt->removeAllViewports(); + rt->addViewport(mCamera); + rt->setAutoUpdated(false); + rt->getViewport(0)->setClearEveryFrame(false); + + // debug overlay + /* + Ogre::Rectangle2D* debugOverlay = new Ogre::Rectangle2D(true); + debugOverlay->setCorners(w*2-1, 0.9, (w+0.18)*2-1, 0.4, false); + w += 0.2; + debugOverlay->setBoundingBox(aabInf); + Ogre::SceneNode* debugNode = mMainSceneMgr->getRootSceneNode()->createChildSceneNode(); + debugNode->attachObject(debugOverlay); + + Ogre::MaterialPtr debugMaterial = Ogre::MaterialManager::getSingleton().create("RippleDebug" + Ogre::StringConverter::toString(i), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + if (i != 3) + debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleHeight" + Ogre::StringConverter::toString(i)); + else + debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleNormal"); + debugMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + + debugOverlay->setMaterial("RippleDebug" + Ogre::StringConverter::toString(i)); + */ + + mRenderTargets[i] = rt; + mTextures[i] = texture; + } + + sh::Factory::getInstance().setSharedParameter("rippleTextureSize", sh::makeProperty( + new sh::Vector4(1.0/512, 1.0/512, 512, 512))); + sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty( + new sh::Vector3(0, 0, 0))); + sh::Factory::getInstance().setSharedParameter("rippleAreaLength", sh::makeProperty( + new sh::FloatValue(mRippleAreaLength))); + +} + +RippleSimulation::~RippleSimulation() +{ + delete mRectangle; + + Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); +} + +void RippleSimulation::update(float dt, Ogre::Vector2 position) +{ + // try to keep 20 fps + mTime += dt; + + while (mTime >= 1/20.0 || mFirstUpdate) + { + mPreviousFrameOffset = mCurrentFrameOffset; + + mCurrentFrameOffset = position - mRippleCenter; + // add texel offsets from previous frame. + mCurrentFrameOffset += mTexelOffset; + + mTexelOffset = Ogre::Vector2(std::fmod(mCurrentFrameOffset.x, 1.0f/mTextureSize), + std::fmod(mCurrentFrameOffset.y, 1.0f/mTextureSize)); + + // now subtract new offset in order to snap to texels + mCurrentFrameOffset -= mTexelOffset; + + // texture coordinate space + mCurrentFrameOffset /= mRippleAreaLength; + + mRippleCenter = position; + + addImpulses(); + waterSimulation(); + heightMapToNormalMap(); + + swapHeightMaps(); + if (!mFirstUpdate) + mTime -= 1/20.0; + else + mFirstUpdate = false; + } + + sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty( + new sh::Vector3(mRippleCenter.x + mTexelOffset.x, mRippleCenter.y + mTexelOffset.y, 0))); +} + +void RippleSimulation::addImpulses() +{ + mRectangle->setVisible(false); + mImpulse->setVisible(true); + + /// \todo it should be more efficient to render all emitters at once + for (std::vector::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it) + { + if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()) + { + // fetch a new ptr (to handle cell change etc) + // for non-player actors this is done in updateObjectCell + it->mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + } + float* _currentPos = it->mPtr.getRefData().getPosition().pos; + Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]); + + if ( (currentPos - it->mLastEmitPosition).length() > 2 + && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), currentPos)) + { + it->mLastEmitPosition = currentPos; + + Ogre::Vector2 pos (currentPos.x, currentPos.y); + pos -= mRippleCenter; + pos /= mRippleAreaLength; + float size = mImpulseSize / mRippleAreaLength; + mImpulse->setCorners(pos.x-size, pos.y+size, pos.x+size, pos.y-size, false); + + // don't render if we are offscreen + if (pos.x - size >= 1.0 || pos.y+size <= -1.0 || pos.x+size <= -1.0 || pos.y-size >= 1.0) + continue; + mRenderTargets[1]->update(); + } + } + + mImpulse->setVisible(false); + mRectangle->setVisible(true); +} + +void RippleSimulation::waterSimulation() +{ + mRectangle->setMaterial("HeightmapSimulation"); + + sh::Factory::getInstance().setTextureAlias("Heightmap0", mTextures[0]->getName()); + sh::Factory::getInstance().setTextureAlias("Heightmap1", mTextures[1]->getName()); + + sh::Factory::getInstance().setSharedParameter("currentFrameOffset", sh::makeProperty( + new sh::Vector3(mCurrentFrameOffset.x, mCurrentFrameOffset.y, 0))); + sh::Factory::getInstance().setSharedParameter("previousFrameOffset", sh::makeProperty( + new sh::Vector3(mPreviousFrameOffset.x, mPreviousFrameOffset.y, 0))); + + mRenderTargets[2]->update(); +} + +void RippleSimulation::heightMapToNormalMap() +{ + mRectangle->setMaterial("HeightToNormalMap"); + + sh::Factory::getInstance().setTextureAlias("Heightmap2", mTextures[2]->getName()); + + mRenderTargets[TEX_NORMAL]->update(); +} + +void RippleSimulation::swapHeightMaps() +{ + // 0 -> 1 -> 2 to 2 -> 0 ->1 + Ogre::RenderTexture* tmp = mRenderTargets[0]; + Ogre::TexturePtr tmp2 = mTextures[0]; + + mRenderTargets[0] = mRenderTargets[1]; + mTextures[0] = mTextures[1]; + + mRenderTargets[1] = mRenderTargets[2]; + mTextures[1] = mTextures[2]; + + mRenderTargets[2] = tmp; + mTextures[2] = tmp2; +} + +void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) +{ + Emitter newEmitter; + newEmitter.mPtr = ptr; + newEmitter.mScale = scale; + newEmitter.mForce = force; + newEmitter.mLastEmitPosition = Ogre::Vector3(0,0,0); + mEmitters.push_back (newEmitter); +} + +void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + { + if (it->mPtr == ptr) + { + mEmitters.erase(it); + return; + } + } +} + +void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + { + if (it->mPtr == old) + { + it->mPtr = ptr; + return; + } + } +} + + +} diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp new file mode 100644 index 000000000..7e7eebc1c --- /dev/null +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -0,0 +1,85 @@ +#ifndef RIPPLE_SIMULATION_H +#define RIPPLE_SIMULATION_H + +#include +#include +#include +#include + +#include "../mwworld/ptr.hpp" + +namespace Ogre +{ + class RenderTexture; + class Camera; + class SceneManager; + class Rectangle2D; +} + +namespace MWRender +{ + +struct Emitter +{ + MWWorld::Ptr mPtr; + Ogre::Vector3 mLastEmitPosition; + float mScale; + float mForce; +}; + +class RippleSimulation +{ +public: + RippleSimulation(Ogre::SceneManager* mainSceneManager); + ~RippleSimulation(); + + void update(float dt, Ogre::Vector2 position); + + /// adds an emitter, position will be tracked automatically + void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter (const MWWorld::Ptr& ptr); + void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + +private: + std::vector mEmitters; + + Ogre::RenderTexture* mRenderTargets[4]; + Ogre::TexturePtr mTextures[4]; + + int mTextureSize; + float mRippleAreaLength; + float mImpulseSize; + + bool mFirstUpdate; + + Ogre::Camera* mCamera; + + // own scenemanager to render our simulation + Ogre::SceneManager* mSceneMgr; + Ogre::Rectangle2D* mRectangle; + + // scenemanager to create the debug overlays on + Ogre::SceneManager* mMainSceneMgr; + + static const int TEX_NORMAL = 3; + + Ogre::Rectangle2D* mImpulse; + + void addImpulses(); + void heightMapToNormalMap(); + void waterSimulation(); + void swapHeightMaps(); + + float mTime; + + Ogre::Vector2 mRippleCenter; + + Ogre::Vector2 mTexelOffset; + + Ogre::Vector2 mCurrentFrameOffset; + Ogre::Vector2 mPreviousFrameOffset; +}; + +} + +#endif diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 3d9f13243..595a82294 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -9,9 +9,6 @@ #include #include -#include -#include - #include #include "renderconst.hpp" @@ -33,8 +30,8 @@ void Shadows::recreate() // Split shadow maps are currently disabled because the terrain cannot cope with them // (Too many texture units) Solution would be a multi-pass terrain material - bool split = Settings::Manager::getBool("split", "Shadows"); - //const bool split = false; + //bool split = Settings::Manager::getBool("split", "Shadows"); + const bool split = false; sh::Factory::getInstance ().setGlobalSetting ("shadows", enabled && !split ? "true" : "false"); sh::Factory::getInstance ().setGlobalSetting ("shadows_pssm", enabled && split ? "true" : "false"); @@ -125,6 +122,7 @@ void Shadows::recreate() // -------------------------------------------------------------------------------------------------------------------- // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- // -------------------------------------------------------------------------------------------------------------------- + /* if (Settings::Manager::getBool("debug", "Shadows")) { OverlayManager& mgr = OverlayManager::getSingleton(); @@ -181,6 +179,7 @@ void Shadows::recreate() if ((overlay = mgr.getByName("DebugOverlay"))) mgr.destroy(overlay); } + */ } PSSMShadowCameraSetup* Shadows::getPSSMSetup() diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 60ecd4303..604ad50d2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include @@ -110,7 +110,7 @@ void BillboardObject::setPosition(const Vector3& pPosition) Vector3 BillboardObject::getPosition() const { Vector3 p = mNode->_getDerivedPosition() - mNode->getParentSceneNode()->_getDerivedPosition(); - return Vector3(p.x, -p.z, p.y); + return p; } void BillboardObject::setVisibilityFlags(int flags) @@ -203,49 +203,7 @@ unsigned int Moon::getPhaseInt() const return 0; } -void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) -{ - // Get the vertex colour buffer of this mesh - const Ogre::VertexElement* ves_diffuse = ent->getMesh()->getSubMesh(0)->vertexData->vertexDeclaration->findElementBySemantic( Ogre::VES_DIFFUSE ); - HardwareVertexBufferSharedPtr colourBuffer = ent->getMesh()->getSubMesh(0)->vertexData->vertexBufferBinding->getBuffer(ves_diffuse->getSource()); - - // Lock - void* pData = colourBuffer->lock(HardwareBuffer::HBL_NORMAL); - - // Iterate over all vertices - int vertex_size = colourBuffer->getVertexSize(); - float * currentVertex = NULL; - for (unsigned int i=0; igetNumVertices(); ++i) - { - // Get a pointer to the vertex colour - ves_diffuse->baseVertexPointerToElement( pData, ¤tVertex ); - - unsigned char alpha=0; - if (meshType == 0) alpha = i%2 ? 0 : 255; // this is a cylinder, so every second vertex belongs to the bottom-most row - else if (meshType == 1) - { - if (i>= 49 && i <= 64) alpha = 0; // bottom-most row - else if (i>= 33 && i <= 48) alpha = 64; // second bottom-most row - else alpha = 255; - } - // NB we would have to swap R and B depending on rendersystem specific VertexElementType, but doesn't matter since they are both 1 - uint8 tmpR = static_cast(255); - uint8 tmpG = static_cast(255); - uint8 tmpB = static_cast(255); - uint8 tmpA = static_cast(alpha); - - // Modify - *((uint32*)currentVertex) = tmpR | (tmpG << 8) | (tmpB << 16) | (tmpA << 24); - - // Move to the next vertex - pData = static_cast (pData) + vertex_size; - } - - // Unlock - ent->getMesh()->getSubMesh(0)->vertexData->vertexBufferBinding->getBuffer(ves_diffuse->getSource())->unlock(); -} - -SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) +SkyManager::SkyManager (SceneNode* root, Camera* pCamera) : mHour(0.0f) , mDay(0) , mMonth(0) @@ -276,9 +234,8 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mCloudAnimationTimer(0.f) , mMoonRed(false) { - mSceneMgr = pMwRoot->getCreator(); + mSceneMgr = root->getCreator(); mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); } @@ -297,6 +254,7 @@ void SkyManager::create() sh::Factory::getInstance().setSharedParameter ("nightFade", sh::makeProperty(new sh::FloatValue(0))); sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4(0,0,0,1))); + sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty(new sh::Vector4(0,0,0,1))); sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", ""); sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); @@ -322,15 +280,19 @@ void SkyManager::create() mSunGlare->setRenderQueue(RQG_SkiesLate); mSunGlare->setVisibilityFlags(RV_NoReflection); + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite (); + // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mAtmosphereNight, NULL, "meshes\\sky_night_01.nif"); + NifOgre::EntityList entities = NifOgre::Loader::createEntities(mAtmosphereNight, "meshes\\sky_night_01.nif"); for(size_t i = 0, matidx = 0;i < entities.mEntities.size();i++) { Entity* night1_ent = entities.mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); + night1_ent->getMesh()->_setBounds (aabInf); for (unsigned int j=0; jgetNumSubEntities(); ++j) { @@ -349,30 +311,35 @@ void SkyManager::create() // Atmosphere (day) mAtmosphereDay = mRootNode->createChildSceneNode(); - entities = NifOgre::NIFLoader::createEntities(mAtmosphereDay, NULL, "meshes\\sky_atmosphere.nif"); + entities = NifOgre::Loader::createEntities(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); for(size_t i = 0;i < entities.mEntities.size();i++) { Entity* atmosphere_ent = entities.mEntities[i]; atmosphere_ent->setCastShadows(false); atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); atmosphere_ent->setVisibilityFlags(RV_Sky); - atmosphere_ent->getSubEntity (0)->setMaterialName ("openmw_atmosphere"); - ModVertexAlpha(atmosphere_ent, 0); + + for(unsigned int j = 0;j < atmosphere_ent->getNumSubEntities();j++) + atmosphere_ent->getSubEntity (j)->setMaterialName("openmw_atmosphere"); + + // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions + atmosphere_ent->getMesh()->_setBounds (aabInf); } // Clouds SceneNode* clouds_node = mRootNode->createChildSceneNode(); - entities = NifOgre::NIFLoader::createEntities(clouds_node, NULL, "meshes\\sky_clouds_01.nif"); + entities = NifOgre::Loader::createEntities(clouds_node, "meshes\\sky_clouds_01.nif"); for(size_t i = 0;i < entities.mEntities.size();i++) { Entity* clouds_ent = entities.mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); - clouds_ent->getSubEntity(0)->setMaterialName ("openmw_clouds"); + for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++) + clouds_ent->getSubEntity(j)->setMaterialName("openmw_clouds"); clouds_ent->setCastShadows(false); - - ModVertexAlpha(clouds_ent, 1); + // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions + clouds_ent->getMesh()->_setBounds (aabInf); } mCreated = true; @@ -406,7 +373,7 @@ void SkyManager::update(float duration) mRootNode->setPosition(mCamera->getDerivedPosition()); // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * (MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); + mCloudAnimationTimer += duration * mCloudSpeed; sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", sh::makeProperty(new sh::FloatValue(mCloudAnimationTimer))); @@ -434,7 +401,6 @@ void SkyManager::update(float duration) // increase the strength of the sun glare effect depending // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); - sun = Vector3(sun.x, sun.z, -sun.y); Vector3 cam = mCamera->getRealDirection(); const Degree angle = sun.angleBetween( cam ); float val = 1- (angle.valueDegrees() / 180.f); @@ -521,6 +487,13 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, weather.mSkyColor.a))); } + if (mFogColour != weather.mFogColor) + { + mFogColour = weather.mFogColor; + sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty(new sh::Vector4( + weather.mFogColor.r, weather.mFogColor.g, weather.mFogColor.b, weather.mFogColor.a))); + } + mCloudSpeed = weather.mCloudSpeed; if (weather.mNight && mStarsOpacity != weather.mNightFade) @@ -628,6 +601,10 @@ void SkyManager::setLightningStrength(const float factor) else mLightning->setVisible(false); } +void SkyManager::setLightningEnabled(bool enabled) +{ + /// \todo +} void SkyManager::setLightningDirection(const Ogre::Vector3& dir) { diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index ee1360853..66557a6c1 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -112,7 +112,7 @@ namespace MWRender class SkyManager { public: - SkyManager(Ogre::SceneNode* pMwRoot, Ogre::Camera* pCamera); + SkyManager(Ogre::SceneNode* root, Ogre::Camera* pCamera); ~SkyManager(); void update(float duration); @@ -167,6 +167,7 @@ namespace MWRender void setLightningStrength(const float factor); void setLightningDirection(const Ogre::Vector3& dir); + void setLightningEnabled(bool enabled); ///< disable prior to map render void setGlare(const float glare); void setGlareEnabled(bool enabled); @@ -210,6 +211,7 @@ namespace MWRender float mStarsOpacity; Ogre::ColourValue mCloudColour; Ogre::ColourValue mSkyColour; + Ogre::ColourValue mFogColour; Ogre::Light* mLightning; @@ -218,8 +220,6 @@ namespace MWRender float mGlare; // target float mGlareFade; // actual - void ModVertexAlpha(Ogre::Entity* ent, unsigned int meshType); - bool mEnabled; bool mSunEnabled; bool mMasserEnabled; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index e5a1362d7..438366873 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwworld/esmstore.hpp" @@ -25,7 +26,7 @@ namespace MWRender //---------------------------------------------------------------------------------------------- TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) : - mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)), mRendering(rend) + mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Y, mLandSize, mWorldSize)), mRendering(rend) { mTerrainGlobals = OGRE_NEW TerrainGlobalOptions(); @@ -39,9 +40,10 @@ namespace MWRender ->getActiveProfile(); mActiveProfile = static_cast(activeProfile); - //The pixel error should be as high as possible without it being noticed - //as it governs how fast mesh quality decreases. - mTerrainGlobals->setMaxPixelError(8); + // We don't want any pixel error at all. Really, LOD makes no sense here - morrowind uses 65x65 verts in one cell, + // so applying LOD is most certainly slower than doing no LOD at all. + // Setting this to 0 seems to cause glitches though. :/ + mTerrainGlobals->setMaxPixelError(1); mTerrainGlobals->setLayerBlendMapSize(32); @@ -53,8 +55,8 @@ namespace MWRender mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); mTerrainGroup.setOrigin(Vector3(mWorldSize/2, - 0, - -mWorldSize/2)); + mWorldSize/2, + 0)); Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); @@ -143,7 +145,7 @@ namespace MWRender std::map indexes; initTerrainTextures(&terrainData, cellX, cellY, x * numTextures, y * numTextures, - numTextures, indexes); + numTextures, indexes, land->mPlugin); if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) { @@ -178,6 +180,16 @@ namespace MWRender } } + // when loading from a heightmap, Ogre::Terrain does not update the derived data (normal map, LOD) + // synchronously, even if we supply synchronous = true parameter to loadTerrain. + // the following to be the only way to make sure derived data is ready when rendering the next frame. + while (mTerrainGroup.isDerivedDataUpdateInProgress()) + { + // we need to wait for this to finish + OGRE_THREAD_SLEEP(5); + Root::getSingleton().getWorkQueue()->processResponses(); + } + mTerrainGroup.freeTemporaryResources(); } @@ -202,8 +214,13 @@ namespace MWRender void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes) + std::map& indexes, size_t plugin) { + // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code + // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need + // to adopt the following code for a dynamic palette. And this is evil - the current design + // does not work well for this task... + assert(terrainData != NULL && "Must have valid terrain data"); assert(fromX >= 0 && fromY >= 0 && "Can't get a terrain texture on terrain outside the current cell"); @@ -216,12 +233,20 @@ namespace MWRender // //If we don't sort the ltex indexes, the splatting order may differ between //cells which may lead to inconsistent results when shading between cells + int num = MWBase::Environment::get().getWorld()->getStore().get().getSize(plugin); std::set ltexIndexes; for ( int y = fromY - 1; y < fromY + size + 1; y++ ) { for ( int x = fromX - 1; x < fromX + size + 1; x++ ) { - ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + int idx = getLtexIndexAt(cellX, cellY, x, y); + // This is a quick hack to prevent the program from trying to fetch textures + // from a neighboring cell, which might originate from a different plugin, + // and use a separate texture palette. Right now, we simply cast it to the + // default texture (i.e. 0). + if (idx > num) + idx = 0; + ltexIndexes.insert(idx); } } @@ -233,7 +258,7 @@ namespace MWRender iter != ltexIndexes.end(); ++iter ) { - const uint16_t ltexIndex = *iter; + uint16_t ltexIndex = *iter; //this is the base texture, so we can ignore this at present if ( ltexIndex == baseTexture ) { @@ -249,8 +274,11 @@ namespace MWRender const MWWorld::Store <exStore = MWBase::Environment::get().getWorld()->getStore().get(); - assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 && - "LAND.VTEX must be within the bounds of the LTEX array"); + // NOTE: using the quick hack above, we should no longer end up with textures indices + // that are out of bounds. However, I haven't updated the test to a multi-palette + // system yet. We probably need more work here, so we skip it for now. + //assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 && + //"LAND.VTEX must be within the bounds of the LTEX array"); std::string texture; if ( ltexIndex == 0 ) @@ -259,7 +287,7 @@ namespace MWRender } else { - texture = ltexStore.search(ltexIndex-1)->mTexture; + texture = ltexStore.search(ltexIndex-1, plugin)->mTexture; //TODO this is needed due to MWs messed up texture handling texture = texture.substr(0, texture.rfind(".")) + ".dds"; } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index c83d96cf4..484a0dbe3 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -75,7 +75,7 @@ namespace MWRender{ void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes); + std::map& indexes, size_t plugin); /** * Creates the blend (splatting maps) for the given terrain from the ltex data. diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 5ef9fe58f..8a568883d 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -119,10 +119,6 @@ namespace MWRender shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); } - // caustics - sh::MaterialInstanceTextureUnit* caustics = p->createTextureUnit ("causticMap"); - caustics->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("water_nm.png"))); - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty(new sh::StringValue( Ogre::StringConverter::toString(numBlendTextures + numLayers + 2)))); @@ -153,9 +149,8 @@ namespace MWRender --freeTextureUnits; // colourmap --freeTextureUnits; - freeTextureUnits -= 3; // shadow PSSM - - --freeTextureUnits; // caustics + // shadow + --freeTextureUnits; // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) return static_cast(freeTextureUnits / (1.25f)); diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 3e31b2a58..fe1214cf5 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -67,6 +67,7 @@ namespace MWRender void setGlobalColourMapEnabled(bool enabled); void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name); + virtual void setLightmapEnabled(bool) {} private: sh::MaterialInstance* mMaterial; diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp new file mode 100644 index 000000000..2cbc85cd3 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -0,0 +1,1164 @@ +#include "videoplayer.hpp" + +#define __STDC_CONSTANT_MACROS +#include + +#include +#include + +#include +#include + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwsound/sound_decoder.hpp" +#include "../mwsound/sound.hpp" + +#include "renderconst.hpp" + +#ifdef _WIN32 +#include + +typedef SSIZE_T ssize_t; +#endif + +namespace MWRender +{ + +#ifdef OPENMW_USE_FFMPEG + +extern "C" +{ +#include +#include +#include +} + +#define MAX_AUDIOQ_SIZE (5 * 16 * 1024) +#define MAX_VIDEOQ_SIZE (5 * 256 * 1024) +#define AV_SYNC_THRESHOLD 0.01 +#define AUDIO_DIFF_AVG_NB 20 +#define VIDEO_PICTURE_QUEUE_SIZE 1 + +enum { + AV_SYNC_AUDIO_MASTER, + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_MASTER, + + AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER +}; + + +struct PacketQueue { + PacketQueue() + : first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0) + { } + ~PacketQueue() + { clear(); } + + AVPacketList *first_pkt, *last_pkt; + volatile bool flushing; + int nb_packets; + int size; + + boost::mutex mutex; + boost::condition_variable cond; + + void put(AVPacket *pkt); + int get(AVPacket *pkt, VideoState *is); + + void flush(); + void clear(); +}; + +struct VideoPicture { + VideoPicture() : pts(0.0) + { } + + std::vector data; + double pts; +}; + +struct VideoState { + VideoState() + : format_ctx(NULL), av_sync_type(AV_SYNC_DEFAULT) + , external_clock_base(0.0) + , audio_st(NULL) + , video_st(NULL), frame_last_pts(0.0), frame_last_delay(0.0), + video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0), + pictq_rindex(0), pictq_windex(0) + , refresh_rate_ms(10), refresh(false), quit(false), display_ready(false) + { + // Register all formats and codecs + av_register_all(); + } + + ~VideoState() + { deinit(); } + + void init(const std::string& resourceName); + void deinit(); + + int stream_open(int stream_index, AVFormatContext *pFormatCtx); + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height); + + static void video_thread_loop(VideoState *is); + static void decode_thread_loop(VideoState *is); + + void video_display(); + void video_refresh_timer(); + + int queue_picture(AVFrame *pFrame, double pts); + double synchronize_video(AVFrame *src_frame, double pts); + + static void video_refresh(VideoState *is); + + + double get_audio_clock() + { return this->AudioTrack->getTimeOffset(); } + + double get_video_clock() + { return this->frame_last_pts; } + + double get_external_clock() + { return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0; } + + double get_master_clock() + { + if(this->av_sync_type == AV_SYNC_VIDEO_MASTER) + return this->get_video_clock(); + if(this->av_sync_type == AV_SYNC_AUDIO_MASTER) + return this->get_audio_clock(); + return this->get_external_clock(); + } + + + static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size); + static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size); + static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence); + + + Ogre::DataStreamPtr stream; + AVFormatContext* format_ctx; + + int av_sync_type; + uint64_t external_clock_base; + + AVStream** audio_st; + PacketQueue audioq; + MWBase::SoundPtr AudioTrack; + + AVStream** video_st; + double frame_last_pts; + double frame_last_delay; + double video_clock; ///pkt = *pkt; + pkt1->next = NULL; + + if(pkt1->pkt.destruct == NULL) + { + if(av_dup_packet(&pkt1->pkt) < 0) + { + av_free(pkt1); + throw std::runtime_error("Failed to duplicate packet"); + } + av_free_packet(pkt); + } + + this->mutex.lock (); + + if(!last_pkt) + this->first_pkt = pkt1; + else + this->last_pkt->next = pkt1; + this->last_pkt = pkt1; + this->nb_packets++; + this->size += pkt1->pkt.size; + this->cond.notify_one(); + + this->mutex.unlock(); +} + +int PacketQueue::get(AVPacket *pkt, VideoState *is) +{ + boost::unique_lock lock(this->mutex); + while(!is->quit) + { + AVPacketList *pkt1 = this->first_pkt; + if(pkt1) + { + this->first_pkt = pkt1->next; + if(!this->first_pkt) + this->last_pkt = NULL; + this->nb_packets--; + this->size -= pkt1->pkt.size; + + *pkt = pkt1->pkt; + av_free(pkt1); + + return 1; + } + + if(this->flushing) + break; + this->cond.wait(lock); + } + + return -1; +} + +void PacketQueue::flush() +{ + this->flushing = true; + this->cond.notify_one(); +} + +void PacketQueue::clear() +{ + AVPacketList *pkt, *pkt1; + + this->mutex.lock(); + for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1) + { + pkt1 = pkt->next; + av_free_packet(&pkt->pkt); + av_freep(&pkt); + } + this->last_pkt = NULL; + this->first_pkt = NULL; + this->nb_packets = 0; + this->size = 0; + this->mutex.unlock (); +} + + +class MovieAudioDecoder : public MWSound::Sound_Decoder +{ + static void fail(const std::string &str) + { + throw std::runtime_error(str); + } + + struct AutoAVPacket : public AVPacket { + AutoAVPacket(int size=0) + { + if(av_new_packet(this, size) < 0) + throw std::bad_alloc(); + } + ~AutoAVPacket() + { av_free_packet(this); } + }; + + VideoState *mVideoState; + AVStream *mAVStream; + + AutoAVPacket mPacket; + AVFrame *mFrame; + ssize_t mFramePos; + ssize_t mFrameSize; + + double mAudioClock; + + /* averaging filter for audio sync */ + double mAudioDiffAccum; + const double mAudioDiffAvgCoef; + const double mAudioDiffThreshold; + int mAudioDiffAvgCount; + + /* Add or subtract samples to get a better sync, return number of bytes to + * skip (negative means to duplicate). */ + int synchronize_audio() + { + if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER) + return 0; + + int sample_skip = 0; + + // accumulate the clock difference + double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock(); + mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum; + if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB) + mAudioDiffAvgCount++; + else + { + double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef); + if(fabs(avg_diff) >= mAudioDiffThreshold) + { + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n); + } + } + + return sample_skip; + } + + int audio_decode_frame(AVFrame *frame) + { + AVPacket *pkt = &mPacket; + + for(;;) + { + while(pkt->size > 0) + { + int len1, got_frame; + + len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt); + if(len1 < 0) break; + + if(len1 <= pkt->size) + { + /* Move the unread data to the front and clear the end bits */ + int remaining = pkt->size - len1; + memmove(pkt->data, &pkt->data[len1], remaining); + av_shrink_packet(pkt, remaining); + } + + /* No data yet? Look for more frames */ + if(!got_frame || frame->nb_samples <= 0) + continue; + + mAudioClock += (double)frame->nb_samples / + (double)mAVStream->codec->sample_rate; + + /* We have data, return it and come back for more later */ + return frame->nb_samples * mAVStream->codec->channels * + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + } + av_free_packet(pkt); + + /* next packet */ + if(mVideoState->audioq.get(pkt, mVideoState) < 0) + return -1; + + /* if update, update the audio clock w/pts */ + if((uint64_t)pkt->pts != AV_NOPTS_VALUE) + mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts; + } + } + + void open(const std::string&) +#ifdef _WIN32 + { fail(std::string("Invalid call to ")+__FUNCSIG__); } +#else + { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } +#endif + + void close() { } + + std::string getName() + { return mVideoState->stream->getName(); } + + void rewind() { } + +public: + MovieAudioDecoder(VideoState *is) + : mVideoState(is) + , mAVStream(*is->audio_st) + , mFrame(avcodec_alloc_frame()) + , mFramePos(0) + , mFrameSize(0) + , mAudioClock(0.0) + , mAudioDiffAccum(0.0) + , mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB))) + /* Correct audio only if larger error than this */ + , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) + , mAudioDiffAvgCount(0) + { } + virtual ~MovieAudioDecoder() + { + av_freep(&mFrame); + } + + void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type) + { + if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8) + *type = MWSound::SampleType_UInt8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16) + *type = MWSound::SampleType_Int16; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT) + *type = MWSound::SampleType_Float32; + else + fail(std::string("Unsupported sample format: ")+ + av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); + + if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO) + *chans = MWSound::ChannelConfig_Stereo; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = MWSound::ChannelConfig_Quad; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = MWSound::ChannelConfig_5point1; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = MWSound::ChannelConfig_7point1; + else if(mAVStream->codec->channel_layout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(mAVStream->codec->channels == 1) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channels == 2) + *chans = MWSound::ChannelConfig_Stereo; + else + { + std::stringstream sstr("Unsupported raw channel count: "); + sstr << mAVStream->codec->channels; + fail(sstr.str()); + } + } + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), mAVStream->codec->channels, + mAVStream->codec->channel_layout); + fail(std::string("Unsupported channel layout: ")+str); + } + + *samplerate = mAVStream->codec->sample_rate; + } + + size_t read(char *stream, size_t len) + { + int sample_skip = synchronize_audio(); + size_t total = 0; + + while(total < len) + { + if(mFramePos >= mFrameSize) + { + /* We have already sent all our data; get more */ + mFrameSize = audio_decode_frame(mFrame); + if(mFrameSize < 0) + { + /* If error, we're done */ + break; + } + + mFramePos = std::min(mFrameSize, sample_skip); + sample_skip -= mFramePos; + continue; + } + + size_t len1 = len - total; + if(mFramePos >= 0) + { + len1 = std::min(len1, mFrameSize-mFramePos); + memcpy(stream, mFrame->data[0]+mFramePos, len1); + } + else + { + len1 = std::min(len1, -mFramePos); + + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + + /* add samples by copying the first sample*/ + if(n == 1) + memset(stream, *mFrame->data[0], len1); + else if(n == 2) + { + const int16_t val = *((int16_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int16_t*)(stream+nb)) = val; + } + else if(n == 4) + { + const int32_t val = *((int32_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int32_t*)(stream+nb)) = val; + } + else if(n == 8) + { + const int64_t val = *((int64_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int64_t*)(stream+nb)) = val; + } + else + { + for(size_t nb = 0;nb < len1;nb += n) + memcpy(stream+nb, mFrame->data[0], n); + } + } + + total += len1; + stream += len1; + mFramePos += len1; + } + + return total; + } + + size_t getSampleOffset() + { + ssize_t clock_delay = (mFrameSize-mFramePos) / mAVStream->codec->channels / + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay; + } +}; + + +int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->read(buf, buf_size); +} + +int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->write(buf, buf_size); +} + +int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + + whence &= ~AVSEEK_FORCE; + if(whence == AVSEEK_SIZE) + return stream->size(); + if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + + +void VideoState::video_refresh(VideoState* is) +{ + boost::system_time t = boost::get_system_time(); + while(!is->quit) + { + t += boost::posix_time::milliseconds(is->refresh_rate_ms); + boost::this_thread::sleep(t); + is->refresh = true; + } +} + + +void VideoState::video_display() +{ + VideoPicture *vp = &this->pictq[this->pictq_rindex]; + + if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("VideoTexture"); + if(texture.isNull() || static_cast(texture->getWidth()) != (*this->video_st)->codec->width + || static_cast(texture->getHeight()) != (*this->video_st)->codec->height) + { + Ogre::TextureManager::getSingleton ().remove ("VideoTexture"); + texture = Ogre::TextureManager::getSingleton().createManual( + "VideoTexture", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + (*this->video_st)->codec->width, (*this->video_st)->codec->height, + 0, + Ogre::PF_BYTE_RGBA, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + } + Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); + Ogre::HardwarePixelBufferSharedPtr buffer = texture->getBuffer(); + buffer->blitFromMemory(pb); + this->display_ready = true; + } +} + +void VideoState::video_refresh_timer() +{ + VideoPicture *vp; + double delay; + + if(this->pictq_size == 0) + return; + + vp = &this->pictq[this->pictq_rindex]; + + delay = vp->pts - this->frame_last_pts; /* the pts from last time */ + if(delay <= 0 || delay >= 1.0) { + /* if incorrect delay, use previous one */ + delay = this->frame_last_delay; + } + /* save for next time */ + this->frame_last_delay = delay; + this->frame_last_pts = vp->pts; + + /* FIXME: Syncing should be done in the decoding stage, where frames can be + * skipped or duplicated as needed. */ + /* update delay to sync to audio if not master source */ + if(this->av_sync_type != AV_SYNC_VIDEO_MASTER) + { + double diff = this->get_video_clock() - this->get_master_clock(); + + /* Skip or repeat the frame. Take delay into account + * FFPlay still doesn't "know if this is the best guess." */ + double sync_threshold = std::max(delay, AV_SYNC_THRESHOLD); + if(diff <= -sync_threshold) + delay = 0; + else if(diff >= sync_threshold) + delay = 2 * delay; + } + + this->refresh_rate_ms = std::max(1, (int)(delay*1000.0)); + /* show the picture! */ + this->video_display(); + + /* update queue for next picture! */ + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size--; + this->pictq_cond.notify_one(); + this->pictq_mutex.unlock(); +} + + +int VideoState::queue_picture(AVFrame *pFrame, double pts) +{ + VideoPicture *vp; + + /* wait until we have a new pic */ + { + boost::unique_lock lock(this->pictq_mutex); + while(this->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !this->quit) + this->pictq_cond.timed_wait(lock, boost::posix_time::milliseconds(1)); + } + if(this->quit) + return -1; + + // windex is set to 0 initially + vp = &this->pictq[this->pictq_windex]; + + // Convert the image into RGBA format for Ogre + if(this->sws_context == NULL) + { + int w = (*this->video_st)->codec->width; + int h = (*this->video_st)->codec->height; + this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt, + w, h, PIX_FMT_RGBA, SWS_BICUBIC, + NULL, NULL, NULL); + if(this->sws_context == NULL) + throw std::runtime_error("Cannot initialize the conversion context!\n"); + } + + vp->pts = pts; + vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4); + + uint8_t *dst = &vp->data[0]; + sws_scale(this->sws_context, pFrame->data, pFrame->linesize, + 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize); + + // now we inform our display thread that we have a pic ready + this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size++; + this->pictq_mutex.unlock(); + + return 0; +} + +double VideoState::synchronize_video(AVFrame *src_frame, double pts) +{ + double frame_delay; + + /* if we have pts, set video clock to it */ + if(pts != 0) + this->video_clock = pts; + else + pts = this->video_clock; + + /* update the video clock */ + frame_delay = av_q2d((*this->video_st)->codec->time_base); + + /* if we are repeating a frame, adjust clock accordingly */ + frame_delay += src_frame->repeat_pict * (frame_delay * 0.5); + this->video_clock += frame_delay; + + return pts; +} + + +/* These are called whenever we allocate a frame + * buffer. We use this to store the global_pts in + * a frame at the time it is allocated. + */ +static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; +static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + int ret = avcodec_default_get_buffer(c, pic); + uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t)); + *pts = global_video_pkt_pts; + pic->opaque = pts; + return ret; +} +static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + if(pic) av_freep(&pic->opaque); + avcodec_default_release_buffer(c, pic); +} + + +void VideoState::video_thread_loop(VideoState *self) +{ + AVPacket pkt1, *packet = &pkt1; + int frameFinished; + AVFrame *pFrame; + double pts; + + pFrame = avcodec_alloc_frame(); + + self->rgbaFrame = avcodec_alloc_frame(); + avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); + + while(self->videoq.get(packet, self) >= 0) + { + // Save global pts to be stored in pFrame + global_video_pkt_pts = packet->pts; + // Decode video frame + if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0) + throw std::runtime_error("Error decoding video frame"); + + pts = 0; + if((uint64_t)packet->dts != AV_NOPTS_VALUE) + pts = packet->dts; + else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) + pts = *(uint64_t*)pFrame->opaque; + pts *= av_q2d((*self->video_st)->time_base); + + av_free_packet(packet); + + // Did we get a video frame? + if(frameFinished) + { + pts = self->synchronize_video(pFrame, pts); + if(self->queue_picture(pFrame, pts) < 0) + break; + } + } + + av_free(pFrame); + + avpicture_free((AVPicture*)self->rgbaFrame); + av_free(self->rgbaFrame); +} + +void VideoState::decode_thread_loop(VideoState *self) +{ + AVFormatContext *pFormatCtx = self->format_ctx; + AVPacket pkt1, *packet = &pkt1; + + try + { + if(!self->video_st && !self->audio_st) + throw std::runtime_error("No streams to decode"); + + // main decode loop + while(!self->quit) + { + if((self->audio_st >= 0 && self->audioq.size > MAX_AUDIOQ_SIZE) || + (self->video_st >= 0 && self->videoq.size > MAX_VIDEOQ_SIZE)) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + continue; + } + + if(av_read_frame(pFormatCtx, packet) < 0) + break; + + // Is this a packet from the video stream? + if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams) + self->videoq.put(packet); + else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams) + self->audioq.put(packet); + else + av_free_packet(packet); + } + + /* all done - wait for it */ + self->videoq.flush(); + self->audioq.flush(); + while(!self->quit) + { + // EOF reached, all packets processed, we can exit now + if(self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0) + break; + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + catch(std::runtime_error& e) { + std::cerr << "An error occured playing the video: " << e.what () << std::endl; + } + catch(Ogre::Exception& e) { + std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl; + } + + self->quit = true; +} + + +bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) +{ + if(this->quit) + return false; + + if(this->refresh) + { + this->refresh = false; + this->video_refresh_timer(); + // Would be nice not to do this all the time... + if(this->display_ready) + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("VideoTexture"); + + // Correct aspect ratio by adding black bars + double videoaspect = av_q2d((*this->video_st)->codec->sample_aspect_ratio); + if(videoaspect == 0.0) + videoaspect = 1.0; + videoaspect *= static_cast((*this->video_st)->codec->width) / (*this->video_st)->codec->height; + + double screenaspect = static_cast(screen_width) / screen_height; + double aspect_correction = videoaspect / screenaspect; + + rect->setCorners(std::max(-1.0, -1.0 * aspect_correction), std::min( 1.0, 1.0 / aspect_correction), + std::min( 1.0, 1.0 * aspect_correction), std::max(-1.0, -1.0 / aspect_correction)); + } + return true; +} + + +int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) +{ + MWSound::DecoderPtr decoder; + AVCodecContext *codecCtx; + AVCodec *codec; + + if(stream_index < 0 || stream_index >= static_cast(pFormatCtx->nb_streams)) + return -1; + + // Get a pointer to the codec context for the video stream + codecCtx = pFormatCtx->streams[stream_index]->codec; + codec = avcodec_find_decoder(codecCtx->codec_id); + if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) + { + fprintf(stderr, "Unsupported codec!\n"); + return -1; + } + + switch(codecCtx->codec_type) + { + case AVMEDIA_TYPE_AUDIO: + this->audio_st = pFormatCtx->streams + stream_index; + + decoder.reset(new MovieAudioDecoder(this)); + this->AudioTrack = MWBase::Environment::get().getSoundManager()->playTrack(decoder, MWBase::SoundManager::Play_TypeMovie); + if(!this->AudioTrack) + { + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + return -1; + } + break; + + case AVMEDIA_TYPE_VIDEO: + this->video_st = pFormatCtx->streams + stream_index; + + this->frame_last_delay = 40e-3; + + codecCtx->get_buffer = our_get_buffer; + codecCtx->release_buffer = our_release_buffer; + this->video_thread = boost::thread(video_thread_loop, this); + this->refresh_thread = boost::thread(video_refresh, this); + break; + + default: + break; + } + + return 0; +} + +void VideoState::init(const std::string& resourceName) +{ + int video_index = -1; + int audio_index = -1; + unsigned int i; + + this->av_sync_type = AV_SYNC_DEFAULT; + this->refresh_rate_ms = 10; + this->refresh = false; + this->quit = false; + + this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName); + if(this->stream.isNull()) + throw std::runtime_error("Failed to open video resource"); + + AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek); + if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext"); + + this->format_ctx = avformat_alloc_context(); + if(this->format_ctx) + this->format_ctx->pb = ioCtx; + + // Open video file + /// \todo leak here, ffmpeg or valgrind bug ? + if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) + { + // "Note that a user-supplied AVFormatContext will be freed on failure." + this->format_ctx = NULL; + av_free(ioCtx); + throw std::runtime_error("Failed to open video input"); + } + + // Retrieve stream information + if(avformat_find_stream_info(this->format_ctx, NULL) < 0) + throw std::runtime_error("Failed to retrieve stream information"); + + // Dump information about file onto standard error + av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0); + + for(i = 0;i < this->format_ctx->nb_streams;i++) + { + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) + video_index = i; + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) + audio_index = i; + } + + this->external_clock_base = av_gettime(); + if(audio_index >= 0) + this->stream_open(audio_index, this->format_ctx); + if(video_index >= 0) + this->stream_open(video_index, this->format_ctx); + + this->parse_thread = boost::thread(decode_thread_loop, this); +} + +void VideoState::deinit() +{ + this->quit = true; + + this->audioq.cond.notify_one(); + this->videoq.cond.notify_one(); + + this->parse_thread.join(); + this->video_thread.join(); + this->refresh_thread.join(); + + if(this->audio_st) + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + if(this->video_st) + avcodec_close((*this->video_st)->codec); + this->video_st = NULL; + + if(this->sws_context) + sws_freeContext(this->sws_context); + this->sws_context = NULL; + + if(this->format_ctx) + { + AVIOContext *ioContext = this->format_ctx->pb; + avformat_close_input(&this->format_ctx); + av_free(ioContext); + } +} + +#else // defined OPENMW_USE_FFMPEG + +class VideoState +{ +public: + VideoState() { } + + void init(const std::string& resourceName) + { + throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\""); + } + void deinit() { } + + void close() { } + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + { return false; } +}; + +#endif // defined OPENMW_USE_FFMPEG + + +VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr) + : mState(NULL) + , mSceneMgr(sceneMgr) + , mVideoMaterial(NULL) + , mRectangle(NULL) + , mNode(NULL) + , mAllowSkipping(false) +{ + mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General"); + if (mVideoMaterial.isNull ()) + { + mVideoMaterial = Ogre::MaterialManager::getSingleton().create("VideoMaterial", "General"); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + Ogre::MaterialPtr blackMaterial = Ogre::MaterialManager::getSingleton().getByName("BlackBarsMaterial", "General"); + if (blackMaterial.isNull ()) + { + blackMaterial = Ogre::MaterialManager::getSingleton().create("BlackBarsMaterial", "General"); + blackMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->createTextureUnitState()->setTextureName("black.png"); + } + + mRectangle = new Ogre::Rectangle2D(true); + mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mRectangle->setMaterial("VideoMaterial"); + mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+2); + mBackgroundRectangle = new Ogre::Rectangle2D(true); + mBackgroundRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mBackgroundRectangle->setMaterial("BlackBarsMaterial"); + mBackgroundRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1); + + // Use infinite AAB to always stay visible + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + mRectangle->setBoundingBox(aabInf); + mBackgroundRectangle->setBoundingBox(aabInf); + + // Attach background to the scene + mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mNode->attachObject(mRectangle); + mBackgroundNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mBackgroundNode->attachObject(mBackgroundRectangle); + + mRectangle->setVisible(false); + mRectangle->setVisibilityFlags(RV_Overlay); + mBackgroundRectangle->setVisible(false); + mBackgroundRectangle->setVisibilityFlags(RV_Overlay); +} + +VideoPlayer::~VideoPlayer() +{ + if(mState) + close(); + + mSceneMgr->destroySceneNode(mNode); + mSceneMgr->destroySceneNode(mBackgroundNode); + + delete mRectangle; + delete mBackgroundRectangle; +} + +void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) +{ + mAllowSkipping = allowSkipping; + + if(mState) + close(); + + mRectangle->setVisible(true); + mBackgroundRectangle->setVisible(true); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Video); + + // Turn off rendering except the GUI + mSceneMgr->clearSpecialCaseRenderQueues(); + // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work. + for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) + { + if(i > 0 && i < 96) + mSceneMgr->addSpecialCaseRenderQueue(i); + } + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + + MWBase::Environment::get().getSoundManager()->pauseSounds(); + + try { + mState = new VideoState; + mState->init(resourceName); + } + catch(std::exception& e) { + std::cerr<< "Failed to play video: "<update(mVideoMaterial, mRectangle, mWidth, mHeight)) + close(); + } +} + +void VideoPlayer::stopVideo () +{ + if (mAllowSkipping) + close(); +} + +void VideoPlayer::close() +{ + if(mState) + { + mState->deinit(); + + delete mState; + mState = NULL; + } + + MWBase::Environment::get().getSoundManager()->resumeSounds(); + + mRectangle->setVisible(false); + mBackgroundRectangle->setVisible(false); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Video); + + mSceneMgr->clearSpecialCaseRenderQueues(); + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); +} + +bool VideoPlayer::isPlaying () +{ + return mState != NULL; +} + +} diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp new file mode 100644 index 000000000..5e9d18ba4 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.hpp @@ -0,0 +1,52 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include + +namespace Ogre +{ + class SceneManager; + class SceneNode; + class Rectangle2D; +} + +namespace MWRender +{ + struct VideoState; + + class VideoPlayer + { + public: + VideoPlayer(Ogre::SceneManager* sceneMgr); + ~VideoPlayer(); + + void playVideo (const std::string& resourceName, bool allowSkipping); + + void update(); + + void close(); + void stopVideo(); + + bool isPlaying(); + + void setResolution (int w, int h) { mWidth = w; mHeight = h; } + + + private: + VideoState* mState; + + bool mAllowSkipping; + + Ogre::SceneManager* mSceneMgr; + Ogre::MaterialPtr mVideoMaterial; + Ogre::Rectangle2D* mRectangle; + Ogre::Rectangle2D* mBackgroundRectangle; + Ogre::SceneNode* mNode; + Ogre::SceneNode* mBackgroundNode; + + int mWidth; + int mHeight; + }; +} + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e8f099640..c8b9db7f1 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -4,17 +4,12 @@ #include #include #include -#include -#include -#include #include -#include -#include -#include #include "sky.hpp" #include "renderingmanager.hpp" -#include "compositors.hpp" +#include "ripplesimulation.hpp" +#include "refraction.hpp" #include #include @@ -27,38 +22,199 @@ using namespace Ogre; namespace MWRender { -Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cell) : - mCamera (camera), mSceneManager (camera->getSceneManager()), - mIsUnderwater(false), mVisibilityFlags(0), - mReflectionTarget(0), mActive(1), mToggled(1), - mReflectionRenderActive(false), mRendering(rend), - mWaterTimer(0.f) +CubeReflection::CubeReflection(Ogre::SceneManager* sceneManager) + : Reflection(sceneManager) { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton ().createManual("CubeReflection", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_CUBE_MAP, + 512,512, 0, PF_R8G8B8, TU_RENDERTARGET); + + mCamera = mSceneMgr->createCamera ("CubeCamera"); + mCamera->setNearClipDistance (5); + mCamera->setFarClipDistance (1000); + + for (int face = 0; face < 6; ++face) + { + mRenderTargets[face] = texture->getBuffer (face)->getRenderTarget(); + mRenderTargets[face]->removeAllViewports (); + Viewport* vp = mRenderTargets[face]->addViewport (mCamera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setMaterialScheme ("water_reflection"); + mRenderTargets[face]->setAutoUpdated(false); + + /* + Vector3 lookAt(0,0,0), up(0,0,0), right(0,0,0); + switch(face) + { + case 0: lookAt.x =-1; up.y = 1; right.z = 1; break; // +X + case 1: lookAt.x = 1; up.y = 1; right.z =-1; break; // -X + case 2: lookAt.y =-1; up.z = 1; right.x = 1; break; // +Y + case 3: lookAt.y = 1; up.z =-1; right.x = 1; break; // -Y + case 4: lookAt.z = 1; up.y = 1; right.x =-1; break; // +Z + case 5: lookAt.z =-1; up.y = 1; right.x =-1; break; // -Z + } + Quaternion orient(right, up, lookAt); + mCamera->setOrientation(orient); + */ + } +} + +CubeReflection::~CubeReflection () +{ + Ogre::TextureManager::getSingleton ().remove("CubeReflection"); + mSceneMgr->destroyCamera (mCamera); +} + +void CubeReflection::update () +{ + mParentCamera->getParentSceneNode ()->needUpdate (); + mCamera->setPosition(mParentCamera->getDerivedPosition()); +} + +// -------------------------------------------------------------------------------------------------------------------------------- + +PlaneReflection::PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky) + : Reflection(sceneManager) + , mSky(sky) + , mRenderActive(false) +{ + mCamera = mSceneMgr->createCamera ("PlaneReflectionCamera"); + mSceneMgr->addRenderQueueListener(this); + + mTexture = TextureManager::getSingleton().createManual("WaterReflection", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET); + + mRenderTarget = mTexture->getBuffer()->getRenderTarget(); + Viewport* vp = mRenderTarget->addViewport(mCamera); + vp->setOverlaysEnabled(false); + vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); + vp->setShadowsEnabled(false); + vp->setMaterialScheme("water_reflection"); + mRenderTarget->addListener(this); + mRenderTarget->setActive(true); + mRenderTarget->setAutoUpdated(true); + + sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mTexture->getName()); +} + +PlaneReflection::~PlaneReflection () +{ + mRenderTarget->removeListener (this); + mSceneMgr->destroyCamera (mCamera); + mSceneMgr->removeRenderQueueListener(this); + TextureManager::getSingleton ().remove("WaterReflection"); +} + +void PlaneReflection::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) +{ + // We don't want the sky to get clipped by custom near clip plane (the water plane) + if (queueGroupId < 20 && mRenderActive) + { + mCamera->disableCustomNearClipPlane(); + Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + } +} + +void PlaneReflection::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) +{ + if (queueGroupId < 20 && mRenderActive) + { + mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane); + Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + } +} + +void PlaneReflection::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) +{ + mParentCamera->getParentSceneNode ()->needUpdate (); + mCamera->setOrientation(mParentCamera->getDerivedOrientation()); + mCamera->setPosition(mParentCamera->getDerivedPosition()); + mCamera->setNearClipDistance(mParentCamera->getNearClipDistance()); + mCamera->setFarClipDistance(mParentCamera->getFarClipDistance()); + mCamera->setAspectRatio(mParentCamera->getAspectRatio()); + mCamera->setFOVy(mParentCamera->getFOVy()); + mRenderActive = true; + + Vector3 pos = mParentCamera->getRealPosition(); + pos.y = (mWaterPlane).d*2 - pos.y; + mSky->setSkyPosition(pos); + mCamera->enableReflection(mWaterPlane); + + // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane. + // since all we are interested in is depth, we only need the third row of the matrix. + Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix (); + sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]); + sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (row3)); + + // enable clip plane here to take advantage of CPU culling for overwater or underwater objects + mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane); +} + +void PlaneReflection::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) +{ + mSky->resetSkyPosition(); + mCamera->disableReflection(); + mCamera->disableCustomNearClipPlane(); + mRenderActive = false; +} + +void PlaneReflection::setHeight (float height) +{ + mWaterPlane = Plane(Ogre::Vector3(0,0,1), height); + mErrorPlane = Plane(Ogre::Vector3(0,0,1), height - 5); + mErrorPlaneUnderwater = Plane(Ogre::Vector3(0,0,-1), -height - 5); +} + +void PlaneReflection::setActive (bool active) +{ + mRenderTarget->setActive(active); +} + +void PlaneReflection::setViewportBackground(Ogre::ColourValue colour) +{ + mRenderTarget->getViewport (0)->setBackgroundColour (colour); +} + +void PlaneReflection::setVisibilityMask (int flags) +{ + mRenderTarget->getViewport (0)->setVisibilityMask (flags); +} + +// -------------------------------------------------------------------------------------------------------------------------------- + +Water::Water (Ogre::Camera *camera, RenderingManager* rend) : + mCamera (camera), mSceneMgr (camera->getSceneManager()), + mIsUnderwater(false), mVisibilityFlags(0), + mActive(1), mToggled(1), + mRendering(rend), + mWaterTimer(0.f), + mReflection(NULL), + mRefraction(NULL), + mSimulation(NULL), + mPlayer(0,0) +{ + mSimulation = new RippleSimulation(mSceneMgr); + mSky = rend->getSkyManager(); mMaterial = MaterialManager::getSingleton().getByName("Water"); - mTop = cell->mWater; + mTop = 0; mIsUnderwater = false; - mWaterPlane = Plane(Vector3::UNIT_Y, 0); + mWaterPlane = Plane(Vector3::UNIT_Z, 0); - MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Z); + MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Y); - mWater = mSceneManager->createEntity("water"); + mWater = mSceneMgr->createEntity("water"); mWater->setVisibilityFlags(RV_Water); - mWater->setRenderQueueGroup(RQG_Water); mWater->setCastShadows(false); + mWater->setRenderQueueGroup(RQG_Alpha); - mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); + mWaterNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mReflectionCamera = mSceneManager->createCamera("ReflectionCamera"); - - if(!(cell->mData.mFlags & cell->Interior)) - { - mWaterNode->setPosition(getSceneNodeCoordinates(cell->mData.mX, cell->mData.mY)); - } mWaterNode->attachObject(mWater); applyRTT(); @@ -66,26 +222,11 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mWater->setMaterial(mMaterial); - /* - Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); - underwaterDome->setRenderQueueGroup (RQG_UnderWater); - mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); - mUnderwaterDome->attachObject (underwaterDome); - mUnderwaterDome->setScale(10000,10000,10000); - mUnderwaterDome->setVisible(false); - underwaterDome->setMaterialName("Underwater_Dome"); - */ - - mSceneManager->addRenderQueueListener(this); - - assignTextures(); - setHeight(mTop); sh::MaterialInstance* m = sh::Factory::getInstance ().getMaterialInstance ("Water"); m->setListener (this); - // ---------------------------------------------------------------------------------------------- // ---------------------------------- reflection debug overlay ---------------------------------- // ---------------------------------------------------------------------------------------------- @@ -146,12 +287,12 @@ Water::~Water() { MeshManager::getSingleton().remove("water"); - if (mReflectionTarget) - mReflectionTexture->getBuffer()->getRenderTarget()->removeListener(this); - mWaterNode->detachObject(mWater); - mSceneManager->destroyEntity(mWater); - mSceneManager->destroySceneNode(mWaterNode); + mSceneMgr->destroyEntity(mWater); + mSceneMgr->destroySceneNode(mWaterNode); + + delete mReflection; + delete mRefraction; } void Water::changeCell(const ESM::Cell* cell) @@ -168,12 +309,14 @@ void Water::setHeight(const float height) { mTop = height; - mWaterPlane = Plane(Vector3::UNIT_Y, height); + mWaterPlane = Plane(Vector3::UNIT_Z, -height); - // small error due to reflection texture size & reflection distortion - mErrorPlane = Plane(Vector3::UNIT_Y, height - 5); + if (mReflection) + mReflection->setHeight(height); + if (mRefraction) + mRefraction->setHeight(height); - mWaterNode->setPosition(0, height, 0); + mWaterNode->setPosition(0, 0, height); sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); } @@ -194,137 +337,76 @@ Water::updateUnderwater(bool underwater) mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID; + if (mReflection) + mReflection->setUnderwater (mIsUnderwater); + if (mRefraction) + mRefraction->setUnderwater (mIsUnderwater); + updateVisible(); } Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY) { - return Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2)); -} - -void Water::preRenderTargetUpdate(const RenderTargetEvent& evt) -{ - if (evt.source == mReflectionTarget) - { - mCamera->getParentSceneNode ()->needUpdate (); - mReflectionCamera->setOrientation(mCamera->getDerivedOrientation()); - mReflectionCamera->setPosition(mCamera->getDerivedPosition()); - mReflectionCamera->setNearClipDistance(mCamera->getNearClipDistance()); - mReflectionCamera->setFarClipDistance(mCamera->getFarClipDistance()); - mReflectionCamera->setAspectRatio(mCamera->getAspectRatio()); - mReflectionCamera->setFOVy(mCamera->getFOVy()); - mReflectionRenderActive = true; - - Vector3 pos = mCamera->getRealPosition(); - pos.y = mTop*2 - pos.y; - mSky->setSkyPosition(pos); - mReflectionCamera->enableReflection(mWaterPlane); - } -} - -void Water::postRenderTargetUpdate(const RenderTargetEvent& evt) -{ - if (evt.source == mReflectionTarget) - { - mSky->resetSkyPosition(); - mReflectionCamera->disableReflection(); - mReflectionCamera->disableCustomNearClipPlane(); - mReflectionRenderActive = false; - } -} - -void Water::assignTextures() -{ - if (Settings::Manager::getBool("shader", "Water")) - { - - CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mRendering->getViewport())->getCompositor("gbuffer"); - - TexturePtr colorTexture = compositor->getTextureInstance("mrt_output", 0); - sh::Factory::getInstance ().setTextureAlias ("WaterRefraction", colorTexture->getName()); - - TexturePtr depthTexture = compositor->getTextureInstance("mrt_output", 1); - sh::Factory::getInstance ().setTextureAlias ("SceneDepth", depthTexture->getName()); - } + return Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), gridY * CELL_SIZE + (CELL_SIZE / 2), mTop); } void Water::setViewportBackground(const ColourValue& bg) { - if (mReflectionTarget) - mReflectionTarget->getViewport(0)->setBackgroundColour(bg); + if (mReflection) + mReflection->setViewportBackground(bg); } void Water::updateVisible() { mWater->setVisible(mToggled && mActive); - if (mReflectionTarget) - mReflectionTarget->setActive(mToggled && mActive); + if (mReflection) + mReflection->setActive(mToggled && mActive); + if (mRefraction) + mRefraction->setActive(mToggled && mActive); } -void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) +void Water::update(float dt, Ogre::Vector3 player) { - // We don't want the sky to get clipped by custom near clip plane (the water plane) - if (queueGroupId < 20 && mReflectionRenderActive) - { - mReflectionCamera->disableCustomNearClipPlane(); - Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); - } -} - -void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) -{ - if (queueGroupId < 20 && mReflectionRenderActive) - { - if (!mIsUnderwater) - mReflectionCamera->enableCustomNearClipPlane(mErrorPlane); - Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); - } -} - -void Water::update(float dt) -{ - /* - Ogre::Vector3 pos = mCamera->getDerivedPosition (); - pos.y = -mWaterPlane.d; - mUnderwaterDome->setPosition (pos); - */ - - mWaterTimer += dt / 30.0 * MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + mWaterTimer += dt; sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); mRendering->getSkyManager ()->setGlareEnabled (!mIsUnderwater); + + mPlayer = Ogre::Vector2(player.x, player.y); +} + +void Water::frameStarted(float dt) +{ + mSimulation->update(dt, mPlayer); + + if (mReflection) + mReflection->update(); } void Water::applyRTT() { - if (mReflectionTarget) - { - TextureManager::getSingleton().remove("WaterReflection"); - mReflectionTarget = 0; - } + delete mReflection; + mReflection = NULL; + delete mRefraction; + mRefraction = NULL; // Create rendertarget for reflection - int rttsize = Settings::Manager::getInt("rtt size", "Water"); + //int rttsize = Settings::Manager::getInt("rtt size", "Water"); if (Settings::Manager::getBool("shader", "Water")) { - mReflectionTexture = TextureManager::getSingleton().createManual("WaterReflection", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, rttsize, rttsize, 0, PF_A8R8G8B8, TU_RENDERTARGET); + mReflection = new PlaneReflection(mSceneMgr, mSky); + mReflection->setParentCamera (mCamera); + mReflection->setHeight(mTop); - RenderTarget* rtt = mReflectionTexture->getBuffer()->getRenderTarget(); - Viewport* vp = rtt->addViewport(mReflectionCamera); - vp->setOverlaysEnabled(false); - vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); - vp->setShadowsEnabled(false); - // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) - vp->setMaterialScheme("water_reflection"); - rtt->addListener(this); - rtt->setActive(true); - - mReflectionTarget = rtt; - - sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mReflectionTexture->getName()); + if (Settings::Manager::getBool("refraction", "Water")) + { + mRefraction = new Refraction(mCamera); + mRefraction->setHeight(mTop); + } } + + updateVisible(); } void Water::applyVisibilityMask() @@ -336,10 +418,8 @@ void Water::applyVisibilityMask() + RV_Misc * Settings::Manager::getBool("reflect misc", "Water") + RV_Sky; - if (mReflectionTarget) - { - mReflectionTexture->getBuffer()->getRenderTarget()->getViewport(0)->setVisibilityMask(mVisibilityFlags); - } + if (mReflection) + mReflection->setVisibilityMask(mVisibilityFlags); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -350,7 +430,8 @@ void Water::processChangedSettings(const Settings::CategorySettingVector& settin it != settings.end(); ++it) { if ( it->first == "Water" && ( - it->second == "shader" + it->second == "shader" + || it->second == "refraction" || it->second == "rtt size")) applyRT = true; @@ -368,7 +449,6 @@ void Water::processChangedSettings(const Settings::CategorySettingVector& settin applyRTT(); applyVisibilityMask(); mWater->setMaterial(mMaterial); - assignTextures(); } if (applyVisMask) applyVisibilityMask(); @@ -399,4 +479,19 @@ void Water::createdConfiguration (sh::MaterialInstance* m, const std::string& co } } +void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) +{ + mSimulation->addEmitter (ptr, scale, force); +} + +void Water::removeEmitter (const MWWorld::Ptr& ptr) +{ + mSimulation->removeEmitter (ptr); +} + +void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +{ + mSimulation->updateEmitterPtr(old, ptr); +} + } // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index f1e3d7a3b..6c0323637 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -7,13 +7,17 @@ #include #include #include +#include #include #include +#include + + #include "renderconst.hpp" -#include +#include "../mwworld/ptr.hpp" namespace Ogre { @@ -22,6 +26,7 @@ namespace Ogre class SceneNode; class Entity; class Vector3; + class Rectangle2D; struct RenderTargetEvent; } @@ -29,22 +34,82 @@ namespace MWRender { class SkyManager; class RenderingManager; + class RippleSimulation; + class Refraction; + + class Reflection + { + public: + Reflection(Ogre::SceneManager* sceneManager) + : mSceneMgr(sceneManager) {} + virtual ~Reflection() {} + + virtual void setHeight (float height) {} + virtual void setParentCamera (Ogre::Camera* parent) { mParentCamera = parent; } + void setUnderwater(bool underwater) { mIsUnderwater = underwater; } + virtual void setActive (bool active) {} + virtual void setViewportBackground(Ogre::ColourValue colour) {} + virtual void update() {} + virtual void setVisibilityMask (int flags) {} + + protected: + Ogre::Camera* mCamera; + Ogre::Camera* mParentCamera; + Ogre::TexturePtr mTexture; + Ogre::SceneManager* mSceneMgr; + bool mIsUnderwater; + }; + + class CubeReflection : public Reflection + { + public: + CubeReflection(Ogre::SceneManager* sceneManager); + virtual ~CubeReflection(); + + virtual void update(); + protected: + Ogre::RenderTarget* mRenderTargets[6]; + }; + + class PlaneReflection : public Reflection, public Ogre::RenderQueueListener, public Ogre::RenderTargetListener + { + public: + PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky); + virtual ~PlaneReflection(); + + virtual void setHeight (float height); + virtual void setActive (bool active); + virtual void setVisibilityMask (int flags); + + void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + + void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); + void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); + + virtual void setViewportBackground(Ogre::ColourValue colour); + + protected: + Ogre::RenderTarget* mRenderTarget; + SkyManager* mSky; + Ogre::Plane mWaterPlane; + Ogre::Plane mErrorPlane; + Ogre::Plane mErrorPlaneUnderwater; + bool mRenderActive; + }; /// Water rendering - class Water : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener, public sh::MaterialInstanceListener + class Water : public sh::MaterialInstanceListener { static const int CELL_SIZE = 8192; Ogre::Camera *mCamera; - Ogre::SceneManager *mSceneManager; + Ogre::SceneManager *mSceneMgr; Ogre::Plane mWaterPlane; - Ogre::Plane mErrorPlane; Ogre::SceneNode *mWaterNode; Ogre::Entity *mWater; - //Ogre::SceneNode* mUnderwaterDome; - bool mIsUnderwater; bool mActive; bool mToggled; @@ -52,17 +117,10 @@ namespace MWRender { float mWaterTimer; - bool mReflectionRenderActive; Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); protected: - void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - - void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); - void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); - void applyRTT(); void applyVisibilityMask(); @@ -75,24 +133,29 @@ namespace MWRender { Ogre::MaterialPtr mMaterial; - Ogre::Camera* mReflectionCamera; - - Ogre::TexturePtr mReflectionTexture; - Ogre::RenderTarget* mReflectionTarget; - bool mUnderwaterEffect; int mVisibilityFlags; + Reflection* mReflection; + Refraction* mRefraction; + RippleSimulation* mSimulation; + + Ogre::Vector2 mPlayer; + public: - Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cell); + Water (Ogre::Camera *camera, RenderingManager* rend); ~Water(); void setActive(bool active); void toggle(); - void update(float dt); + void update(float dt, Ogre::Vector3 player); + void frameStarted(float dt); - void assignTextures(); + /// adds an emitter, position will be tracked automatically using its scene node + void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter (const MWWorld::Ptr& ptr); + void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); void setViewportBackground(const Ogre::ColourValue& bg); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index aa1d2d030..8402a5b4c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -10,6 +10,11 @@ #include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/aiactivate.hpp" +#include "../mwmechanics/aiescort.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/aitravel.hpp" +#include "../mwmechanics/aiwander.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -20,6 +25,27 @@ namespace MWScript { namespace Ai { + template + class OpAiActivate : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + // discard additional arguments (reset), because we have no idea what they mean. + for (unsigned int i=0; i class OpAiTravel : public Interpreter::Opcode1 { @@ -41,6 +67,9 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i + class OpAiEscortCell : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // discard additional arguments (reset), because we have no idea what they mean. + for (unsigned int i=0; i idleList; + idleList.push_back (0); // why MW, why? + + for (int i=2; i<10 && arg0; ++i) + { + Interpreter::Type_Integer idleValue = runtime[0].mFloat; + idleList.push_back(idleValue); + runtime.pop(); + --arg0; + } + // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i + class OpAiFollow : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // discard additional arguments (reset), because we have no idea what they mean. + for (unsigned int i=0; i + class OpAiFollowCell : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // discard additional arguments (reset), because we have no idea what they mean. + for (unsigned int i=0; i + class OpGetCurrentAIPackage : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getTypeId (); + + runtime.push (value); + } + }; + + template + class OpGetDetected : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Integer value = false; // TODO replace with implementation + + std::cout << "AiGetDetected: " << actorID << ", " << value << std::endl; + + runtime.push (value); + } + }; + const int opcodeAiTravel = 0x20000; const int opcodeAiTravelExplicit = 0x20001; @@ -174,8 +364,20 @@ namespace MWScript const int opcodeAiEscortExplicit = 0x20003; const int opcodeGetAiPackageDone = 0x200007c; const int opcodeGetAiPackageDoneExplicit = 0x200007d; + const int opcodeGetCurrentAiPackage = 0x20001ef; + const int opcodeGetCurrentAiPackageExplicit = 0x20001f0; + const int opcodeGetDetected = 0x20001f1; + const int opcodeGetDetectedExplicit = 0x20001f2; const int opcodeAiWander = 0x20010; const int opcodeAiWanderExplicit = 0x20011; + const int opcodeAIActivate = 0x2001e; + const int opcodeAIActivateExplicit = 0x2001f; + const int opcodeAiEscortCell = 0x20020; + const int opcodeAiEscortCellExplicit = 0x20021; + const int opcodeAiFollow = 0x20022; + const int opcodeAiFollowExplicit = 0x20023; + const int opcodeAiFollowCell = 0x20024; + const int opcodeAiFollowCellExplicit = 0x20025; const int opcodeSetHello = 0x200015e; const int opcodeSetHelloExplicit = 0x200015d; const int opcodeSetFight = 0x200015e; @@ -203,16 +405,26 @@ namespace MWScript void registerExtensions (Compiler::Extensions& extensions) { + extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate, + opcodeAIActivateExplicit); extensions.registerInstruction ("aitravel", "fff/l", opcodeAiTravel, opcodeAiTravelExplicit); extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort, opcodeAiEscortExplicit); + extensions.registerInstruction ("aiescortcell", "ccffff/l", opcodeAiEscortCell, + opcodeAiEscortCellExplicit); extensions.registerInstruction ("aiwander", "fff/llllllllll", opcodeAiWander, opcodeAiWanderExplicit); - + extensions.registerInstruction ("aifollow", "cffff/l", opcodeAiFollow, + opcodeAiFollowExplicit); + extensions.registerInstruction ("aifollowcell", "ccffff/l", opcodeAiFollowCell, + opcodeAiFollowCellExplicit); extensions.registerFunction ("getaipackagedone", 'l', "", opcodeGetAiPackageDone, opcodeGetAiPackageDoneExplicit); - + extensions.registerFunction ("getcurrentaipackage", 'l', "", opcodeGetCurrentAiPackage, + opcodeGetAiPackageDoneExplicit); + extensions.registerFunction ("getdetected", 'l', "c", opcodeGetDetected, + opcodeGetDetectedExplicit); extensions.registerInstruction ("sethello", "l", opcodeSetHello, opcodeSetHelloExplicit); extensions.registerInstruction ("setfight", "l", opcodeSetFight, opcodeSetFightExplicit); extensions.registerInstruction ("setflee", "l", opcodeSetFlee, opcodeSetFleeExplicit); @@ -229,15 +441,28 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { + interpreter.installSegment3 (opcodeAIActivate, new OpAiActivate); + interpreter.installSegment3 (opcodeAIActivateExplicit, new OpAiActivate); interpreter.installSegment3 (opcodeAiTravel, new OpAiTravel); interpreter.installSegment3 (opcodeAiTravelExplicit, new OpAiTravel); interpreter.installSegment3 (opcodeAiEscort, new OpAiEscort); interpreter.installSegment3 (opcodeAiEscortExplicit, new OpAiEscort); + interpreter.installSegment3 (opcodeAiEscortCell, new OpAiEscortCell); + interpreter.installSegment3 (opcodeAiEscortCellExplicit, new OpAiEscortCell); interpreter.installSegment3 (opcodeAiWander, new OpAiWander); interpreter.installSegment3 (opcodeAiWanderExplicit, new OpAiWander); + interpreter.installSegment3 (opcodeAiFollow, new OpAiFollow); + interpreter.installSegment3 (opcodeAiFollowExplicit, new OpAiFollow); + interpreter.installSegment3 (opcodeAiFollowCell, new OpAiFollowCell); + interpreter.installSegment3 (opcodeAiFollowCellExplicit, new OpAiFollowCell); interpreter.installSegment5 (opcodeGetAiPackageDone, new OpGetAiPackageDone); + interpreter.installSegment5 (opcodeGetAiPackageDoneExplicit, new OpGetAiPackageDone); + interpreter.installSegment5 (opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); + interpreter.installSegment5 (opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); + interpreter.installSegment3 (opcodeGetDetected, new OpGetDetected); + interpreter.installSegment3 (opcodeGetDetectedExplicit, new OpGetDetected); interpreter.installSegment5 (opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (opcodeSetHelloExplicit, new OpSetAiSetting(0)); interpreter.installSegment5 (opcodeSetFight, new OpSetAiSetting(1)); diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 6f9253e5d..fc52e5e16 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -9,7 +9,7 @@ #include #include -#include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -27,7 +27,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getWorld()->skipAnimation (ptr); + MWBase::Environment::get().getMechanicsManager()->skipAnimation (ptr); } }; @@ -54,7 +54,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getWorld()->playAnimationGroup (ptr, group, mode, 1); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, 1); } }; @@ -87,7 +87,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getWorld()->playAnimationGroup (ptr, group, mode, loops); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops); } }; diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7f4913b8a..81639b5be 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -3,6 +3,10 @@ #include +#include + +#include + #include #include @@ -10,29 +14,18 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "interpretercontext.hpp" #include "ref.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } -} - namespace MWScript { namespace Container @@ -55,11 +48,43 @@ namespace MWScript if (count<0) throw std::runtime_error ("second argument for AddItem must be non-negative"); + // no-op + if (count == 0) + return; + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item); ref.getPtr().getRefData().setCount (count); + + // Configure item's script variables + std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr()); + if (script != "") + { + const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get().find (script); + ref.getPtr().getRefData().setLocals(*esmscript); + } MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr()); + + // Spawn a messagebox (only for items added to player's inventory) + if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer()) + { + // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory + std::string msgBox; + std::string itemName = MWWorld::Class::get(ref.getPtr()).getName(ref.getPtr()); + if (count == 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); + msgBox = boost::str(boost::format(msgBox) % itemName); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); + msgBox = boost::str(boost::format(msgBox) % count % itemName); + } + + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, std::vector()); + } } }; @@ -80,7 +105,7 @@ namespace MWScript Interpreter::Type_Integer sum = 0; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (toLower(iter->getCellRef().mRefID) == toLower(item)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) sum += iter->getRefData().getCount(); runtime.push (sum); @@ -105,13 +130,24 @@ namespace MWScript if (count<0) throw std::runtime_error ("second argument for RemoveItem must be non-negative"); + // no-op + if (count == 0) + return; + MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + std::string itemName = ""; + + // originalCount holds the total number of items to remove, count holds the remaining number of items to remove + Interpreter::Type_Integer originalCount = count; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end() && count; ++iter) { - if (toLower(iter->getCellRef().mRefID) == toLower(item)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) { + itemName = MWWorld::Class::get(*iter).getName(*iter); + if (iter->getRefData().getCount()<=count) { count -= iter->getRefData().getCount(); @@ -124,9 +160,27 @@ namespace MWScript } } } + + // Spawn a messagebox (only for items added to player's inventory) + if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer()) + { + // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory + std::string msgBox; + int numRemoved = (originalCount - count); + if(numRemoved > 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); + msgBox = boost::str (boost::format(msgBox) % numRemoved % itemName); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); + msgBox = boost::str (boost::format(msgBox) % itemName); + } - // To be fully compatible with original Morrowind, we would need to check if - // count is >= 0 here and throw an exception. But let's be tollerant instead. + if (numRemoved > 0) + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, std::vector()); + } } }; @@ -146,7 +200,7 @@ namespace MWScript MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { - if (toLower(it->getCellRef().mRefID) == toLower(item)) + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) break; } if (it == invStore.end()) @@ -246,7 +300,7 @@ namespace MWScript for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); - if (it != invStore.end() && toLower(it->getCellRef().mRefID) == toLower(item)) + if (it != invStore.end() && Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) { runtime.push(1); return; @@ -264,8 +318,8 @@ namespace MWScript virtual void execute(Interpreter::Runtime &runtime) { MWWorld::Ptr ptr = R()(runtime); - - std::string creatureName = toLower (runtime.getStringLiteral (runtime[0].mInteger)); + + const std::string &name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); @@ -273,7 +327,7 @@ namespace MWScript it != invStore.end(); ++it) { - if (toLower(it->getCellRef().mSoul) == toLower(creatureName)) + if (Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name)) { runtime.push(1); return; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 835a3e3ff..283f149de 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -43,7 +43,15 @@ op 0x2001a: PcExpell op 0x2001b: PcExpell, explicit op 0x2001c: PcClearExpelled op 0x2001d: PcClearExpelled, explicit -op s 0x2001e-0x3ffff unused +op 0x2001e: AIActivate +op 0x2001f: AIActivate, explicit reference +op 0x20020: AiEscortCell +op 0x20021: AiEscortCell, explicit reference +op 0x20022: AiFollow +op 0x20023: AiFollow, explicit reference +op 0x20024: AiFollowCell +op 0x20025: AiFollowCell, explicit reference +op s 0x20026-0x3ffff unused Segment 4: (not implemented yet) @@ -291,8 +299,21 @@ op 0x20001e8: RaiseRank op 0x20001e9: RaiseRank, explicit op 0x20001ea: LowerRank op 0x20001eb: LowerRank, explicit +op 0x20001ec: GetPCCrimeLevel +op 0x20001ed: SetPCCrimeLevel +op 0x20001ee: ModPCCrimeLevel +op 0x20001ef: GetCurrentAIPackage +op 0x20001f0: GetCurrentAIPackage, explicit reference +op 0x20001f1: GetDetected +op 0x20001f2: GetDetected, explicit reference +op 0x20001f3: AddSoulGem +op 0x20001f4: AddSoulGem, explicit reference +op 0x20001f5: RemoveSoulGem +op 0x20001f6: RemoveSoulGem, explicit reference +op 0x20001f7: PlayBink +op 0x20001f8: Drop +op 0x20001f9: Drop, explicit reference +op 0x20001fa: DropSoulGem +op 0x20001fb: DropSoulGem, explicit reference -opcodes 0x20001ec-0x3ffffff unused - - - +opcodes 0x20001fa-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 72c2db164..bfe69c79e 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -102,7 +102,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - boost::algorithm::to_lower(cell); + Misc::StringUtils::toLower(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -115,7 +115,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - boost::algorithm::to_lower(name); + Misc::StringUtils::toLower(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, @@ -181,7 +181,7 @@ opcodeEnableStatsReviewMenu); extensions.registerInstruction ("enablemapmenu", "", opcodeEnableMapMenu); extensions.registerInstruction ("enablestatsmenu", "", opcodeEnableStatsMenu); - extensions.registerInstruction ("enablerestmenu", "", opcodeEnableRest); + extensions.registerInstruction ("enablerest", "", opcodeEnableRest); extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest); extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 577ad008f..c74e3f163 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -11,10 +11,13 @@ #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/npcstats.hpp" + #include "locals.hpp" #include "globalscripts.hpp" @@ -174,6 +177,146 @@ namespace MWScript MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat = value; } + std::vector InterpreterContext::getGlobals () const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getGlobals(); + + } + + char InterpreterContext::getGlobalType (const std::string& name) const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getGlobalVariableType(name); + } + + std::string InterpreterContext::getActionBinding(const std::string& action) const + { + std::vector actions = MWBase::Environment::get().getInputManager()->getActionSorting (); + for (std::vector::const_iterator it = actions.begin(); it != actions.end(); ++it) + { + std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (*it); + if(desc == "") + continue; + + if(desc == action) + return MWBase::Environment::get().getInputManager()->getActionBindingName (*it); + } + + return "None"; + } + + std::string InterpreterContext::getNPCName() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mName; + } + + std::string InterpreterContext::getNPCRace() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mRace; + } + + std::string InterpreterContext::getNPCClass() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mClass; + } + + std::string InterpreterContext::getNPCFaction() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mFaction; + } + + std::string InterpreterContext::getNPCRank() const + { + std::map ranks = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(it->first); + + return faction->mRanks[it->second]; + } + + std::string InterpreterContext::getPCName() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + ESM::NPC player = *world->getPlayer().getPlayer().get()->mBase; + return player.mName; + } + + std::string InterpreterContext::getPCRace() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + std::string race = world->getPlayer().getPlayer().get()->mBase->mRace; + return world->getStore().get().find(race)->mName; + } + + std::string InterpreterContext::getPCClass() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + std::string class_ = world->getPlayer().getPlayer().get()->mBase->mClass; + return world->getStore().get().find(class_)->mName; + } + + std::string InterpreterContext::getPCRank() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + + std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(factionId); + + if(it->second < 0 || it->second > 9) // there are only 10 ranks + return ""; + + return faction->mRanks[it->second]; + } + + std::string InterpreterContext::getPCNextRank() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + + std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(factionId); + + if(it->second < 0 || it->second > 9) + return ""; + + if(it->second <= 8) // If player is at max rank, there is no next rank + return faction->mRanks[it->second + 1]; + else + return faction->mRanks[it->second]; + } + + int InterpreterContext::getPCBounty() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + return MWWorld::Class::get (player).getNpcStats (player).getBounty(); + } + + std::string InterpreterContext::getCurrentCellName() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getCurrentCellName(); + } + bool InterpreterContext::isScriptRunning (const std::string& name) const { return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name); diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 6d97f7949..f0b2758d9 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -75,6 +75,36 @@ namespace MWScript virtual void setGlobalLong (const std::string& name, int value); virtual void setGlobalFloat (const std::string& name, float value); + + virtual std::vector getGlobals () const; + + virtual char getGlobalType (const std::string& name) const; + + virtual std::string getActionBinding(const std::string& action) const; + + virtual std::string getNPCName() const; + + virtual std::string getNPCRace() const; + + virtual std::string getNPCClass() const; + + virtual std::string getNPCFaction() const; + + virtual std::string getNPCRank() const; + + virtual std::string getPCName() const; + + virtual std::string getPCRace() const; + + virtual std::string getPCClass() const; + + virtual std::string getPCRank() const; + + virtual std::string getPCNextRank() const; + + virtual int getPCBounty() const; + + virtual std::string getCurrentCellName() const; virtual bool isScriptRunning (const std::string& name) const; diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp new file mode 100644 index 000000000..53f744323 --- /dev/null +++ b/apps/openmw/mwscript/locals.cpp @@ -0,0 +1,41 @@ +#include "locals.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/scriptmanager.hpp" +#include + +namespace MWScript +{ + void Locals::configure (const ESM::Script& script) + { + mShorts.clear(); + mShorts.resize (script.mData.mNumShorts, 0); + mLongs.clear(); + mLongs.resize (script.mData.mNumLongs, 0); + mFloats.clear(); + mFloats.resize (script.mData.mNumFloats, 0); + } + + bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) + { + Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + char type = locals.getType(var); + if(index != -1) + { + switch(type) + { + case 's': + mShorts.at (index) = val; break; + + case 'l': + mLongs.at (index) = val; break; + + case 'f': + mFloats.at (index) = val; break; + } + return true; + } + return false; + } +} diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index ec02e2f12..e933c727f 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -8,21 +8,16 @@ namespace MWScript { - struct Locals + class Locals { - std::vector mShorts; - std::vector mLongs; - std::vector mFloats; + public: + std::vector mShorts; + std::vector mLongs; + std::vector mFloats; + + void configure (const ESM::Script& script); + bool setVarByInt(const std::string& script, const std::string& var, int val); - void configure (const ESM::Script& script) - { - mShorts.clear(); - mShorts.resize (script.mData.mNumShorts, 0); - mLongs.clear(); - mLongs.resize (script.mData.mNumLongs, 0); - mFloats.clear(); - mFloats.resize (script.mData.mNumFloats, 0); - } }; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b687471aa..f329e30d9 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -14,6 +14,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -25,6 +27,22 @@ namespace MWScript { namespace Misc { + class OpPlayBink : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + bool allowSkipping = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getWorld ()->playVideo (name, allowSkipping); + } + }; + class OpGetPcSleep : public Interpreter::Opcode0 { public: @@ -255,7 +273,7 @@ namespace MWScript static bool sActivate; public: - + virtual void execute(Interpreter::Runtime &runtime) { InterpreterContext& context = @@ -306,6 +324,147 @@ namespace MWScript } }; + template + class OpAddSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string creature = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string gem = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + store.get().find(creature); // This line throws an exception if it can't find the creature + + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem); + + ref.getPtr().getRefData().setCount (1); + + ref.getPtr().getCellRef().mSoul = creature; + + MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr()); + + } + }; + + template + class OpRemoveSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr ptr = R()(runtime); + + std::string soul = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + { + if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) + { + if (iter->getRefData().getCount() <= 1) + iter->getRefData().setCount (0); + else + iter->getRefData().setCount (iter->getRefData().getCount() - 1); + break; + } + } + } + }; + + template + class OpDrop : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr ptr = R()(runtime); + + std::string item = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Integer amount = runtime[0].mInteger; + runtime.pop(); + + MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + { + if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) + { + if(iter->getRefData().getCount() <= amount) + { + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); + iter->getRefData().setCount(0); + } + else + { + int original = iter->getRefData().getCount(); + iter->getRefData().setCount(amount); + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); + iter->getRefData().setCount(original - amount); + } + + break; + } + } + } + }; + + template + class OpDropSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr ptr = R()(runtime); + + std::string soul = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + { + if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) + { + + if(iter->getRefData().getCount() <= 1) + { + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); + iter->getRefData().setCount(0); + } + else + { + int original = iter->getRefData().getCount(); + iter->getRefData().setCount(1); + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); + iter->getRefData().setCount(original - 1); + } + + break; + } + } + } + }; + template class OpGetAttacked : public Interpreter::Opcode0 { @@ -414,6 +573,14 @@ namespace MWScript const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; + const int opcodeAddSoulGem = 0x20001f3; + const int opcodeAddSoulGemExplicit = 0x20001f4; + const int opcodeRemoveSoulGem = 0x20001f5; + const int opcodeRemoveSoulGemExplicit = 0x20001f6; + const int opcodeDrop = 0x20001f8; + const int opcodeDropExplicit = 0x20001f9; + const int opcodeDropSoulGem = 0x20001fa; + const int opcodeDropSoulGemExplicit = 0x20001fb; const int opcodeGetAttacked = 0x20001d3; const int opcodeGetAttackedExplicit = 0x20001d4; const int opcodeGetWeaponDrawn = 0x20001d7; @@ -425,6 +592,8 @@ namespace MWScript const int opcodeSetDeleteExplicit = 0x20001e6; const int opcodeGetSquareRoot = 0x20001e7; + const int opcodePlayBink = 0x20001f7; + void registerExtensions (Compiler::Extensions& extensions) { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); @@ -450,8 +619,13 @@ namespace MWScript extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); + extensions.registerInstruction ("playbink", "Sl", opcodePlayBink); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); extensions.registerFunction ("geteffect", 'l', "l", opcodeGetEffect, opcodeGetEffectExplicit); + extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit); + extensions.registerInstruction ("removesoulgem", "c", opcodeRemoveSoulGem, opcodeRemoveSoulGemExplicit); + extensions.registerInstruction ("drop", "cl", opcodeDrop, opcodeDropExplicit); + extensions.registerInstruction ("dropsoulgem", "c", opcodeDropSoulGem, opcodeDropSoulGemExplicit); extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit); extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit); extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit); @@ -481,10 +655,19 @@ namespace MWScript interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (opcodeWakeUpPc, new OpWakeUpPc); + interpreter.installSegment5 (opcodePlayBink, new OpPlayBink); interpreter.installSegment5 (opcodeGetLocked, new OpGetLocked); interpreter.installSegment5 (opcodeGetLockedExplicit, new OpGetLocked); interpreter.installSegment5 (opcodeGetEffect, new OpGetEffect); interpreter.installSegment5 (opcodeGetEffectExplicit, new OpGetEffect); + interpreter.installSegment5 (opcodeAddSoulGem, new OpAddSoulGem); + interpreter.installSegment5 (opcodeAddSoulGemExplicit, new OpAddSoulGem); + interpreter.installSegment5 (opcodeRemoveSoulGem, new OpRemoveSoulGem); + interpreter.installSegment5 (opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem); + interpreter.installSegment5 (opcodeDrop, new OpDrop); + interpreter.installSegment5 (opcodeDropExplicit, new OpDrop); + interpreter.installSegment5 (opcodeDropSoulGem, new OpDropSoulGem); + interpreter.installSegment5 (opcodeDropSoulGemExplicit, new OpDropSoulGem); interpreter.installSegment5 (opcodeGetAttacked, new OpGetAttacked); interpreter.installSegment5 (opcodeGetAttackedExplicit, new OpGetAttacked); interpreter.installSegment5 (opcodeGetWeaponDrawn, new OpGetWeaponDrawn); diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 1f338edbd..fed5877c4 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -140,25 +140,42 @@ namespace MWScript Compiler::Locals& ScriptManager::getLocals (const std::string& name) { - ScriptCollection::iterator iter = mScripts.find (name); - - if (iter==mScripts.end()) { - if (!compile (name)) - { - /// \todo Handle case of cyclic member variable access. Currently this could look up - /// the whole application in an endless recursion. + ScriptCollection::iterator iter = mScripts.find (name); - // failed -> ignore script from now on. - std::vector empty; - mScripts.insert (std::make_pair (name, std::make_pair (empty, Compiler::Locals()))); - throw std::runtime_error ("failed to compile script " + name); - } - - iter = mScripts.find (name); + if (iter!=mScripts.end()) + return iter->second.second; } - return iter->second.second; + { + std::map::iterator iter = mOtherLocals.find (name); + + if (iter!=mOtherLocals.end()) + return iter->second; + } + + Compiler::Locals locals; + + if (const ESM::Script *script = mStore.get().find (name)) + { + int index = 0; + + for (int i=0; imData.mNumShorts; ++i) + locals.declare ('s', script->mVarNames[index++]); + + for (int i=0; imData.mNumLongs; ++i) + locals.declare ('l', script->mVarNames[index++]); + + for (int i=0; imData.mNumFloats; ++i) + locals.declare ('f', script->mVarNames[index++]); + + std::map::iterator iter = + mOtherLocals.insert (std::make_pair (name, locals)).first; + + return iter->second; + } + + throw std::logic_error ("script " + name + " does not exist"); } GlobalScripts& ScriptManager::getGlobalScripts() diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index c4a016eb7..1a856e0c5 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -47,6 +47,7 @@ namespace MWScript ScriptCollection mScripts; GlobalScripts mGlobalScripts; + std::map mOtherLocals; public: diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 35e66241e..408a6f29d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -118,7 +118,8 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; @@ -144,7 +145,8 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c67782168..530d44c9d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -392,6 +392,46 @@ namespace MWScript } }; + class OpGetPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + runtime.push (static_cast (MWWorld::Class::get (player).getNpcStats (player).getBounty())); + } + }; + + class OpSetPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat); + runtime.pop(); + } + }; + + class OpModPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty()); + runtime.pop(); + } + }; + template class OpAddSpell : public Interpreter::Opcode0 { @@ -445,7 +485,7 @@ namespace MWScript for (MWMechanics::Spells::TIterator iter ( MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().begin()); iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().end(); ++iter) - if (*iter==id) + if (iter->first==id) { value = 1; break; @@ -472,7 +512,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -501,7 +541,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -534,7 +574,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -572,7 +612,7 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); if(factionID!="") { @@ -634,7 +674,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWWorld::Class::get (ptr).getNpcStats (ptr).getBaseDisposition()); + runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); } }; @@ -674,7 +714,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - boost::algorithm::to_lower (factionId); + Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); runtime.push ( @@ -710,7 +750,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - boost::algorithm::to_lower (factionId); + Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value); @@ -745,7 +785,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - boost::algorithm::to_lower (factionId); + Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, @@ -790,11 +830,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - boost::algorithm::to_lower(race); + Misc::StringUtils::toLower(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - boost::algorithm::to_lower(npcRace); + Misc::StringUtils::toLower(npcRace); runtime.push (npcRace == race); } @@ -838,7 +878,7 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); if(factionID!="") { @@ -889,7 +929,7 @@ namespace MWScript if(factionID!="") { std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); expelled.insert(factionID); } } @@ -925,7 +965,7 @@ namespace MWScript if(factionID!="") { std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); - boost::algorithm::to_lower(factionID); + Misc::StringUtils::toLower(factionID); expelled.erase (factionID); } } @@ -1016,6 +1056,10 @@ namespace MWScript const int opcodeModSkill = 0x20000fa; const int opcodeModSkillExplicit = 0x2000115; + const int opcodeGetPCCrimeLevel = 0x20001ec; + const int opcodeSetPCCrimeLevel = 0x20001ed; + const int opcodeModPCCrimeLevel = 0x20001ee; + const int opcodeAddSpell = 0x2000147; const int opcodeAddSpellExplicit = 0x2000148; const int opcodeRemoveSpell = 0x2000149; @@ -1141,6 +1185,10 @@ namespace MWScript opcodeModSkill+i, opcodeModSkillExplicit+i); } + extensions.registerFunction ("getpccrimelevel", 'f', "", opcodeGetPCCrimeLevel); + extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel); + extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); + extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); @@ -1235,6 +1283,10 @@ namespace MWScript interpreter.installSegment5 (opcodeModSkillExplicit+i, new OpModSkill (i)); } + interpreter.installSegment5 (opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel); + interpreter.installSegment5 (opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel); + interpreter.installSegment5 (opcodeModPCCrimeLevel, new OpModPCCrimeLevel); + interpreter.installSegment5 (opcodeAddSpell, new OpAddSpell); interpreter.installSegment5 (opcodeAddSpellExplicit, new OpAddSpell); interpreter.installSegment5 (opcodeRemoveSpell, new OpRemoveSpell); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 2dd8f3e16..d86a6e348 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -119,15 +119,15 @@ namespace MWScript if (axis == "x") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()); } else if (axis == "y") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()); } else if (axis == "z") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); } else throw std::runtime_error ("invalid ration axis: " + axis); @@ -148,15 +148,15 @@ namespace MWScript if (axis=="x") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); } else if (axis=="y") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); } else if (axis=="z") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } else throw std::runtime_error ("invalid ration axis: " + axis); @@ -241,15 +241,15 @@ namespace MWScript if(axis == "x") { - runtime.push(ptr.getCellRef().mPos.pos[0]); + runtime.push(ptr.getRefData().getPosition().pos[0]); } else if(axis == "y") { - runtime.push(ptr.getCellRef().mPos.pos[1]); + runtime.push(ptr.getRefData().getPosition().pos[1]); } else if(axis == "z") { - runtime.push(ptr.getCellRef().mPos.pos[2]); + runtime.push(ptr.getRefData().getPosition().pos[2]); } else throw std::runtime_error ("invalid axis: " + axis); diff --git a/apps/openmw/mwscript/userextensions.hpp b/apps/openmw/mwscript/userextensions.hpp index 3642eb5f4..4bc3b46ec 100644 --- a/apps/openmw/mwscript/userextensions.hpp +++ b/apps/openmw/mwscript/userextensions.hpp @@ -13,7 +13,7 @@ namespace Interpreter namespace MWScript { - /// \brief Temporaty script functionality limited to the console + /// \brief Temporary script functionality limited to the console namespace User { void registerExtensions (Compiler::Extensions& extensions); diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp index 4e73573a7..3f3e3a62d 100644 --- a/apps/openmw/mwsound/audiere_decoder.cpp +++ b/apps/openmw/mwsound/audiere_decoder.cpp @@ -53,6 +53,9 @@ public: : mStream(stream), refs(1) { } virtual ~OgreFile() { } + + Ogre::String getName() + { return mStream->getName(); } }; @@ -60,8 +63,9 @@ void Audiere_Decoder::open(const std::string &fname) { close(); - audiere::FilePtr file(new OgreFile(mResourceMgr.openResource(fname))); - mSoundSource = audiere::OpenSampleSource(file); + mSoundFile = audiere::FilePtr(new OgreFile(mResourceMgr.openResource(fname))); + mSoundSource = audiere::OpenSampleSource(mSoundFile); + mSoundFileName = fname; int channels, srate; audiere::SampleFormat format; @@ -86,9 +90,15 @@ void Audiere_Decoder::open(const std::string &fname) void Audiere_Decoder::close() { + mSoundFile = NULL; mSoundSource = NULL; } +std::string Audiere_Decoder::getName() +{ + return mSoundFileName; +} + void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { *samplerate = mSampleRate; @@ -108,6 +118,11 @@ void Audiere_Decoder::rewind() mSoundSource->reset(); } +size_t Audiere_Decoder::getSampleOffset() +{ + return 0; +} + Audiere_Decoder::Audiere_Decoder() { } diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp index 0ad026d51..f432c32ec 100644 --- a/apps/openmw/mwsound/audiere_decoder.hpp +++ b/apps/openmw/mwsound/audiere_decoder.hpp @@ -12,6 +12,8 @@ namespace MWSound { class Audiere_Decoder : public Sound_Decoder { + std::string mSoundFileName; + audiere::FilePtr mSoundFile; audiere::SampleSourcePtr mSoundSource; int mSampleRate; SampleType mSampleType; @@ -20,10 +22,12 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void rewind(); + virtual size_t getSampleOffset(); Audiere_Decoder& operator=(const Audiere_Decoder &rhs); Audiere_Decoder(const Audiere_Decoder &rhs); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 5f61ab8f0..d5c382a41 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -15,28 +15,6 @@ static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } -struct PacketList { - AVPacket pkt; - PacketList *next; -}; - -struct FFmpeg_Decoder::MyStream { - AVCodecContext *mCodecCtx; - int mStreamIdx; - - PacketList *mPackets; - - char *mDecodedData; - size_t mDecodedDataSize; - - FFmpeg_Decoder *mParent; - - void clearPackets(); - void *getAVAudioData(size_t *length); - size_t readAVAudioData(void *data, size_t length); -}; - - int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; @@ -72,173 +50,102 @@ int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) /* Used by getAV*Data to search for more compressed data, and buffer it in the * correct stream. It won't buffer data for streams that the app doesn't have a * handle for. */ -bool FFmpeg_Decoder::getNextPacket(int streamidx) +bool FFmpeg_Decoder::getNextPacket() { - PacketList *packet; + if(!mStream) + return false; - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - -next_packet: - while(av_read_frame(mFormatCtx, &packet->pkt) >= 0) + int stream_idx = mStream - mFormatCtx->streams; + while(av_read_frame(mFormatCtx, &mPacket) >= 0) { - std::vector::iterator iter = mStreams.begin(); - - /* Check each stream the user has a handle for, looking for the one - * this packet belongs to */ - while(iter != mStreams.end()) + /* Check if the packet belongs to this stream */ + if(stream_idx == mPacket.stream_index) { - if((*iter)->mStreamIdx == packet->pkt.stream_index) - { - PacketList **last; - - last = &(*iter)->mPackets; - while(*last != NULL) - last = &(*last)->next; - - *last = packet; - if((*iter)->mStreamIdx == streamidx) - return true; - - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - goto next_packet; - } - iter++; + if((uint64_t)mPacket.pts != AV_NOPTS_VALUE) + mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts; + return true; } + /* Free the packet and look for another */ - av_free_packet(&packet->pkt); + av_free_packet(&mPacket); } - av_free(packet); return false; } -void FFmpeg_Decoder::MyStream::clearPackets() +bool FFmpeg_Decoder::getAVAudioData() { - while(mPackets) - { - PacketList *self = mPackets; - mPackets = self->next; + int got_frame, len; - av_free_packet(&self->pkt); - av_free(self); - } -} + if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO) + return false; -void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length) -{ - int size; - int len; + do { + if(mPacket.size == 0 && !getNextPacket()) + return false; - if(length) *length = 0; - if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) - return NULL; + /* Decode some data, and check for errors */ + if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0) + return false; - mDecodedDataSize = 0; - -next_packet: - if(!mPackets && !mParent->getNextPacket(mStreamIdx)) - return NULL; - - /* Decode some data, and check for errors */ - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, - &mPackets->pkt)) == 0) - { - PacketList *self; - - if(size > 0) - break; - - /* Packet went unread and no data was given? Drop it and try the next, - * I guess... */ - self = mPackets; - mPackets = self->next; - - av_free_packet(&self->pkt); - av_free(self); - - if(!mPackets) - goto next_packet; - - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - } - - if(len < 0) - return NULL; - - if(len < mPackets->pkt.size) - { /* Move the unread data to the front and clear the end bits */ - int remaining = mPackets->pkt.size - len; - memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining); - memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining); - mPackets->pkt.size -= len; - } - else - { - PacketList *self; + int remaining = mPacket.size - len; + if(remaining <= 0) + av_free_packet(&mPacket); + else + { + memmove(mPacket.data, &mPacket.data[len], remaining); + av_shrink_packet(&mPacket, remaining); + } + } while(got_frame == 0 || mFrame->nb_samples == 0); + mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate; - self = mPackets; - mPackets = self->next; - - av_free_packet(&self->pkt); - av_free(self); - } - - if(size == 0) - goto next_packet; - - /* Set the output buffer size */ - mDecodedDataSize = size; - if(length) *length = mDecodedDataSize; - - return mDecodedData; + return true; } -size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) +size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) { size_t dec = 0; while(dec < length) { /* If there's no decoded data, find some */ - if(mDecodedDataSize == 0) + if(mFramePos >= mFrameSize) { - if(getAVAudioData(NULL) == NULL) + if(!getAVAudioData()) break; + mFramePos = 0; + mFrameSize = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); } - if(mDecodedDataSize > 0) - { - /* Get the amount of bytes remaining to be written, and clamp to - * the amount of decoded data we have */ - size_t rem = length-dec; - if(rem > mDecodedDataSize) - rem = mDecodedDataSize; + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = std::min(length-dec, mFrameSize-mFramePos); - /* Copy the data to the app's buffer and increment */ - if(data != NULL) - { - memcpy(data, mDecodedData, rem); - data = (char*)data + rem; - } - dec += rem; - - /* If there's any decoded data left, move it to the front of the - * buffer for next time */ - if(rem < mDecodedDataSize) - memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem); - mDecodedDataSize -= rem; - } + /* Copy the data to the app's buffer and increment */ + memcpy(data, mFrame->data[0]+mFramePos, rem); + data = (char*)data + rem; + dec += rem; + mFramePos += rem; } /* Return the number of bytes we were able to get */ return dec; } - +static AVSampleFormat ffmpegNonPlanarSampleFormat (AVSampleFormat format) +{ + switch (format) + { + case AV_SAMPLE_FMT_U8P: return AV_SAMPLE_FMT_U8; + case AV_SAMPLE_FMT_S16P: return AV_SAMPLE_FMT_S16; + case AV_SAMPLE_FMT_S32P: return AV_SAMPLE_FMT_S32; + case AV_SAMPLE_FMT_FLTP: return AV_SAMPLE_FMT_FLT; + case AV_SAMPLE_FMT_DBLP: return AV_SAMPLE_FMT_DBL; + default:return format; + } +} void FFmpeg_Decoder::open(const std::string &fname) { @@ -265,140 +172,159 @@ void FFmpeg_Decoder::open(const std::string &fname) { if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - std::auto_ptr stream(new MyStream); - stream->mCodecCtx = mFormatCtx->streams[j]->codec; - stream->mStreamIdx = j; - stream->mPackets = NULL; - - AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); - if(!codec) - { - std::stringstream ss("No codec found for id "); - ss << stream->mCodecCtx->codec_id; - fail(ss.str()); - } - if(avcodec_open(stream->mCodecCtx, codec) < 0) - fail("Failed to open audio codec " + std::string(codec->long_name)); - - stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); - stream->mDecodedDataSize = 0; - - stream->mParent = this; - mStreams.push_back(stream.release()); + mStream = &mFormatCtx->streams[j]; break; } } - if(mStreams.empty()) + if(!mStream) fail("No audio streams in "+fname); + + (*mStream)->codec->request_sample_fmt = ffmpegNonPlanarSampleFormat ((*mStream)->codec->sample_fmt); + + AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); + if(!codec) + { + std::stringstream ss("No codec found for id "); + ss << (*mStream)->codec->codec_id; + fail(ss.str()); + } + if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) + fail("Failed to open audio codec " + std::string(codec->long_name)); + + mFrame = avcodec_alloc_frame(); } catch(std::exception &e) { - av_close_input_file(mFormatCtx); - mFormatCtx = NULL; + avformat_close_input(&mFormatCtx); throw; } } void FFmpeg_Decoder::close() { - while(!mStreams.empty()) - { - MyStream *stream = mStreams.front(); + if(mStream) + avcodec_close((*mStream)->codec); + mStream = NULL; - stream->clearPackets(); - avcodec_close(stream->mCodecCtx); - av_free(stream->mDecodedData); - delete stream; + av_free_packet(&mPacket); + av_freep(&mFrame); - mStreams.erase(mStreams.begin()); - } if(mFormatCtx) { AVIOContext* context = mFormatCtx->pb; + avformat_close_input(&mFormatCtx); av_free(context); - mFormatCtx->pb = NULL; - av_close_input_file(mFormatCtx); } - mFormatCtx = NULL; mDataStream.setNull(); } +std::string FFmpeg_Decoder::getName() +{ + return mFormatCtx->filename; +} + void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - if(mStreams.empty()) + if(!mStream) fail("No audio stream info"); - MyStream *stream = mStreams[0]; - if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; - else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT) + *type = SampleType_Float32; else fail(std::string("Unsupported sample format: ")+ - av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); + av_get_sample_fmt_name((*mStream)->codec->sample_fmt)); - if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; - else if(stream->mCodecCtx->channel_layout == 0) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = ChannelConfig_Quad; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = ChannelConfig_5point1; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = ChannelConfig_7point1; + else if((*mStream)->codec->channel_layout == 0) { /* Unknown channel layout. Try to guess. */ - if(stream->mCodecCtx->channels == 1) + if((*mStream)->codec->channels == 1) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channels == 2) + else if((*mStream)->codec->channels == 2) *chans = ChannelConfig_Stereo; else { std::stringstream sstr("Unsupported raw channel count: "); - sstr << stream->mCodecCtx->channels; + sstr << (*mStream)->codec->channels; fail(sstr.str()); } } else { char str[1024]; - av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels, - stream->mCodecCtx->channel_layout); + av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels, + (*mStream)->codec->channel_layout); fail(std::string("Unsupported channel layout: ")+str); } - *samplerate = stream->mCodecCtx->sample_rate; + *samplerate = (*mStream)->codec->sample_rate; } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { - if(mStreams.empty()) - fail("No audio streams"); - - return mStreams.front()->readAVAudioData(buffer, bytes); + if(!mStream) + fail("No audio stream"); + return readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::readAll(std::vector &output) { - if(mStreams.empty()) - fail("No audio streams"); - MyStream *stream = mStreams.front(); - char *inbuf; - size_t got; + if(!mStream) + fail("No audio stream"); - while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0) + while(getAVAudioData()) + { + size_t got = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + const char *inbuf = reinterpret_cast(mFrame->data[0]); output.insert(output.end(), inbuf, inbuf+got); + } } void FFmpeg_Decoder::rewind() { - av_seek_frame(mFormatCtx, -1, 0, 0); - std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets)); + int stream_idx = mStream - mFormatCtx->streams; + if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0) + fail("Failed to seek in audio stream"); + av_free_packet(&mPacket); + mFrameSize = mFramePos = 0; + mNextPts = 0.0; } -FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) +size_t FFmpeg_Decoder::getSampleOffset() { - static bool done_init = false; + int delay = (mFrameSize-mFramePos) / (*mStream)->codec->channels / + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay; +} + +FFmpeg_Decoder::FFmpeg_Decoder() + : mFormatCtx(NULL) + , mStream(NULL) + , mFrame(NULL) + , mFrameSize(0) + , mFramePos(0) + , mNextPts(0.0) +{ + memset(&mPacket, 0, sizeof(mPacket)); /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ + static bool done_init = false; if(!done_init) { av_register_all(); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 7b028e1d0..32b2797ed 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -10,8 +10,8 @@ #include extern "C" { -#include -#include +#include +#include } #include "sound_decoder.hpp" @@ -22,25 +22,36 @@ namespace MWSound class FFmpeg_Decoder : public Sound_Decoder { AVFormatContext *mFormatCtx; + AVStream **mStream; - struct MyStream; - std::vector mStreams; + AVPacket mPacket; + AVFrame *mFrame; - bool getNextPacket(int streamidx); + int mFrameSize; + int mFramePos; + + double mNextPts; + + bool getNextPacket(); Ogre::DataStreamPtr mDataStream; static int readPacket(void *user_data, uint8_t *buf, int buf_size); static int writePacket(void *user_data, uint8_t *buf, int buf_size); static int64_t seek(void *user_data, int64_t offset, int whence); + bool getAVAudioData(); + size_t readAVAudioData(void *data, size_t length); + virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 7f7a84889..fb187f844 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -155,6 +155,11 @@ void MpgSnd_Decoder::close() mDataStream.setNull(); } +std::string MpgSnd_Decoder::getName() +{ + return mDataStream->getName(); +} + void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mSndFile && !mMpgFile) @@ -213,6 +218,11 @@ void MpgSnd_Decoder::rewind() } } +size_t MpgSnd_Decoder::getSampleOffset() +{ + return 0; +} + MpgSnd_Decoder::MpgSnd_Decoder() : mSndInfo() , mSndFile(NULL) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 09082c2f4..be52f6f49 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -34,11 +34,13 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs); MpgSnd_Decoder(const MpgSnd_Decoder &rhs); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index e5169d878..1dc5f9974 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -61,10 +61,95 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) if(fmtlist[i].chans == chans && fmtlist[i].type == type) return fmtlist[i].format; } + + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const struct { + char name[32]; + ChannelConfig chans; + SampleType type; + } mcfmtlist[] = { + { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, + { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, + { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, + { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, + { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, + { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, + }; + static const size_t mcfmtlistsize = sizeof(mcfmtlist)/sizeof(mcfmtlist[0]); + + for(size_t i = 0;i < mcfmtlistsize;i++) + { + if(mcfmtlist[i].chans == chans && mcfmtlist[i].type == type) + { + ALenum format = alGetEnumValue(mcfmtlist[i].name); + if(format != 0 && format != -1) + return format; + } + } + } + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + { + static const struct { + char name[32]; + ChannelConfig chans; + SampleType type; + } fltfmtlist[] = { + { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 }, + { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 }, + }; + static const size_t fltfmtlistsize = sizeof(fltfmtlist)/sizeof(fltfmtlist[0]); + + for(size_t i = 0;i < fltfmtlistsize;i++) + { + if(fltfmtlist[i].chans == chans && fltfmtlist[i].type == type) + { + ALenum format = alGetEnumValue(fltfmtlist[i].name); + if(format != 0 && format != -1) + return format; + } + } + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const struct { + char name[32]; + ChannelConfig chans; + SampleType type; + } fltmcfmtlist[] = { + { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 }, + { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 }, + { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 }, + }; + static const size_t fltmcfmtlistsize = sizeof(fltmcfmtlist)/sizeof(fltmcfmtlist[0]); + + for(size_t i = 0;i < fltmcfmtlistsize;i++) + { + if(fltmcfmtlist[i].chans == chans && fltmcfmtlist[i].type == type) + { + ALenum format = alGetEnumValue(fltmcfmtlist[i].name); + if(format != 0 && format != -1) + return format; + } + } + } + } + fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")"); return AL_NONE; } +static ALint getBufferSampleCount(ALuint buf) +{ + ALint size, bits, channels; + + alGetBufferi(buf, AL_SIZE, &size); + alGetBufferi(buf, AL_BITS, &bits); + alGetBufferi(buf, AL_CHANNELS, &channels); + throwALerror(); + + return size / channels * 8 / bits; +} + // // A streaming OpenAL sound. // @@ -82,19 +167,26 @@ class OpenAL_SoundStream : public Sound ALsizei mSampleRate; ALuint mBufferSize; + ALuint mSamplesQueued; + DecoderPtr mDecoder; volatile bool mIsFinished; + void updateAll(bool local); + OpenAL_SoundStream(const OpenAL_SoundStream &rhs); OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); + friend class OpenAL_Output; + public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags); virtual ~OpenAL_SoundStream(); virtual void stop(); virtual bool isPlaying(); + virtual double getTimeOffset(); virtual void update(); void play(); @@ -109,7 +201,7 @@ const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - boost::mutex mMutex; + boost::recursive_mutex mMutex; boost::thread mThread; StreamThread() @@ -170,8 +262,9 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) - : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) + : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) + , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -189,6 +282,8 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mBufferSize = static_cast(sBufferLength*srate); mBufferSize = framesToBytes(mBufferSize, chans, type); + + mOutput.mActiveSounds.push_back(this); } catch(std::exception &e) { @@ -209,22 +304,20 @@ OpenAL_SoundStream::~OpenAL_SoundStream() alGetError(); mDecoder->close(); + + mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(), + mOutput.mActiveSounds.end(), this)); } void OpenAL_SoundStream::play() { - std::vector data(mBufferSize); - alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; for(ALuint i = 0;i < sNumBuffers;i++) - { - size_t got; - got = mDecoder->read(&data[0], data.size()); - alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); - } + alBufferData(mBuffers[i], mFormat, this, 0, mSampleRate); throwALerror(); alSourceQueueBuffers(mSource, sNumBuffers, mBuffers); @@ -243,6 +336,7 @@ void OpenAL_SoundStream::stop() alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; mDecoder->rewind(); } @@ -254,11 +348,49 @@ bool OpenAL_SoundStream::isPlaying() alGetSourcei(mSource, AL_SOURCE_STATE, &state); throwALerror(); - if(state == AL_PLAYING) + if(state == AL_PLAYING || state == AL_PAUSED) return true; return !mIsFinished; } +double OpenAL_SoundStream::getTimeOffset() +{ + ALint state = AL_STOPPED; + ALfloat offset = 0.0f; + double t; + + mOutput.mStreamThread->mMutex.lock(); + alGetSourcef(mSource, AL_SEC_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) + t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset; + else + t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; + mOutput.mStreamThread->mMutex.unlock(); + + throwALerror(); + return t; +} + +void OpenAL_SoundStream::updateAll(bool local) +{ + alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance); + alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance); + if(local) + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); + } + else + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + alSourcei(mSource, AL_LOOPING, AL_FALSE); + + update(); +} + void OpenAL_SoundStream::update() { ALfloat gain = mVolume*mBaseVolume; @@ -279,52 +411,58 @@ void OpenAL_SoundStream::update() bool OpenAL_SoundStream::process() { - bool finished = mIsFinished; - ALint processed, state; + try { + bool finished = mIsFinished; + ALint processed, state; - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - throwALerror(); - - if(processed > 0) - { - std::vector data(mBufferSize); - do { - ALuint bufid; - size_t got; - - alSourceUnqueueBuffers(mSource, 1, &bufid); - processed--; - - if(finished) - continue; - - got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - } - } while(processed > 0); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); throwALerror(); - } - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued; - - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - throwALerror(); - if(queued > 0) + if(processed > 0) { - alSourcePlay(mSource); + std::vector data(mBufferSize); + do { + ALuint bufid = 0; + size_t got; + + alSourceUnqueueBuffers(mSource, 1, &bufid); + mSamplesQueued -= getBufferSampleCount(bufid); + processed--; + + if(finished) + continue; + + got = mDecoder->read(&data[0], data.size()); + finished = (got < data.size()); + if(got > 0) + { + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mSamplesQueued += getBufferSampleCount(bufid); + } + } while(processed > 0); throwALerror(); } - } - mIsFinished = finished; - return !finished; + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued = 0; + + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(queued > 0) + alSourcePlay(mSource); + throwALerror(); + } + + mIsFinished = finished; + } + catch(std::exception &e) { + std::cout<< "Error updating stream \""<getName()<<"\"" <removeAll(); - while(!mFreeSources.empty()) - { - alDeleteSources(1, &mFreeSources.front()); - mFreeSources.pop_front(); - } + for(size_t i = 0;i < mFreeSources.size();i++) + alDeleteSources(1, &mFreeSources[i]); + mFreeSources.clear(); mBufferRefs.clear(); mUnusedBuffers.clear(); @@ -642,7 +818,7 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -655,7 +831,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } catch(std::exception &e) { @@ -666,25 +842,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(true); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -693,8 +851,8 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume return sound; } -MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, - float min, float max, int flags) +MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, + float min, float max, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -707,7 +865,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound3D(*this, src, buf)); + sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); } catch(std::exception &e) { @@ -718,26 +876,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre throw; } - alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? - 0.0f : volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(false); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -747,7 +886,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre } -MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src; @@ -757,13 +896,11 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu src = mFreeSources.front(); mFreeSources.pop_front(); + if((flags&MWBase::SoundManager::Play_Loop)) + std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; try { - if((flags&MWBase::SoundManager::Play_Loop)) - std::cout <<"Warning: cannot loop stream "<open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); } catch(std::exception &e) { @@ -771,25 +908,7 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); + sound->updateAll(true); sound->play(); return sound; @@ -814,6 +933,61 @@ void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 } +void OpenAL_Output::pauseSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePausev(sources.size(), &sources[0]); + throwALerror(); + } +} + +void OpenAL_Output::resumeSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePlayv(sources.size(), &sources[0]); + throwALerror(); + } +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index fecffa575..02706b50c 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -33,6 +33,9 @@ namespace MWSound uint64_t mBufferCacheMemSize; + typedef std::vector SoundVec; + SoundVec mActiveSounds; + ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); @@ -42,13 +45,16 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags); virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags); - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); + float vol, float basevol, float pitch, float min, float max, int flags); + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); + virtual void pauseSounds(int types); + virtual void resumeSounds(int types); + OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); @@ -64,7 +70,7 @@ namespace MWSound friend class SoundManager; }; #ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output) +#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) #endif } diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 729147f75..8deaf26a6 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -26,16 +26,22 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; + virtual double getTimeOffset() = 0; void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } - Sound() : mPos(0.0f, 0.0f, 0.0f) - , mVolume(1.0f) - , mBaseVolume(1.0f) - , mPitch(1.0f) - , mMinDistance(20.0f) /* 1 * min_range_scale */ - , mMaxDistance(12750.0f) /* 255 * max_range_scale */ - , mFlags(MWBase::SoundManager::Play_Normal) + MWBase::SoundManager::PlayType getPlayType() const + { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } + + + Sound(const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : mPos(pos) + , mVolume(vol) + , mBaseVolume(basevol) + , mPitch(pitch) + , mMinDistance(mindist) + , mMaxDistance(maxdist) + , mFlags(flags) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 9c28d5ff5..151b58036 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -9,13 +9,17 @@ namespace MWSound { enum SampleType { SampleType_UInt8, - SampleType_Int16 + SampleType_Int16, + SampleType_Float32 }; const char *getSampleTypeName(SampleType type); enum ChannelConfig { ChannelConfig_Mono, - ChannelConfig_Stereo + ChannelConfig_Stereo, + ChannelConfig_Quad, + ChannelConfig_5point1, + ChannelConfig_7point1 }; const char *getChannelConfigName(ChannelConfig config); @@ -29,11 +33,13 @@ namespace MWSound virtual void open(const std::string &fname) = 0; virtual void close() = 0; + virtual std::string getName() = 0; virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; virtual size_t read(char *buffer, size_t bytes) = 0; virtual void readAll(std::vector &output); virtual void rewind() = 0; + virtual size_t getSampleOffset() = 0; Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 2680ec1db..b5ccc946a 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,13 +24,16 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) = 0; virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags) = 0; - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; + float vol, float basevol, float pitch, float min, float max, int flags) = 0; + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; + virtual void pauseSounds(int types) = 0; + virtual void resumeSounds(int types) = 0; + Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8c4798c9d..1b07dfe62 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -52,6 +52,7 @@ namespace MWSound , mMusicVolume(1.0f) , mFootstepsVolume(1.0f) , mVoiceVolume(1.0f) + , mPausedSoundTypes(0) { if(!useSound) return; @@ -86,7 +87,7 @@ namespace MWSound { if(devname.empty()) throw; - std::cout <<"Failed to open device \""<init(); Settings::Manager::setString("device", "Sound", ""); } @@ -137,8 +138,29 @@ namespace MWSound return "Sound/"+snd->mSound; } + // Gets the combined volume settings for the given sound type + float SoundManager::volumeFromType(PlayType type) const + { + float volume = mMasterVolume; + switch(type) + { + case Play_TypeSfx: + volume *= mSFXVolume; + break; + case Play_TypeVoice: + volume *= mVoiceVolume; + break; + case Play_TypeMusic: + case Play_TypeMovie: + volume *= mMusicVolume; + break; + case Play_TypeMask: + break; + } + return volume; + } - bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const + bool SoundManager::isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const { SoundMap::const_iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -165,11 +187,13 @@ namespace MWSound std::cout <<"Playing "<streamSound(filename, basevol, 1.0f, Play_NoEnv); - mMusic->mBaseVolume = basevol; - mMusic->mFlags = Play_NoEnv; + + DecoderPtr decoder = getDecoder(); + decoder->open(filename); + + mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic), + 1.0f, Play_NoEnv|Play_TypeMusic); } catch(std::exception &e) { @@ -205,23 +229,20 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename) + void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename) { if(!mOutput->isInitialized()) return; try { // The range values are not tested - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, - 20.0f, 12750.0f, Play_Normal); - sound->mPos = objpos; - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, + 20.0f, 12750.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -236,12 +257,10 @@ namespace MWSound return; try { - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; - MWBase::SoundPtr sound = mOutput->playSound(filePath, basevol, 1.0f, Play_Normal); - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); } catch(std::exception &e) @@ -250,12 +269,12 @@ namespace MWSound } } - bool SoundManager::sayDone(MWWorld::Ptr ptr) const + bool SoundManager::sayDone(const MWWorld::Ptr &ptr) const { return !isPlaying(ptr, "_say_sound"); } - void SoundManager::stopSay(MWWorld::Ptr ptr) + void SoundManager::stopSay(const MWWorld::Ptr &ptr) { SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -271,26 +290,35 @@ namespace MWSound } + MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + { + MWBase::SoundPtr track; + if(!mOutput->isInitialized()) + return track; + try + { + track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type); + } + catch(std::exception &e) + { + std::cout <<"Sound Error: "<isInitialized()) return sound; try { - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); - - sound = mOutput->playSound(file, volume*basevol, pitch, mode); - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; + std::string file = lookup(soundId, volume, min, max); + sound = mOutput->playSound(file, volume, basevol, pitch, mode|Play_TypeSfx); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) @@ -300,8 +328,8 @@ namespace MWSound return sound; } - MWBase::SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, int mode) + MWBase::SoundPtr SoundManager::playSound3D(const MWWorld::Ptr &ptr, const std::string& soundId, + float volume, float pitch, PlayMode mode) { MWBase::SoundPtr sound; if(!mOutput->isInitialized()) @@ -309,21 +337,13 @@ namespace MWSound try { // Look up the sound in the ESM data - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); + std::string file = lookup(soundId, volume, min, max); const ESM::Position &pos = ptr.getRefData().getPosition();; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); - sound->mPos = objpos; - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; - + sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|Play_TypeSfx); if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else @@ -336,7 +356,7 @@ namespace MWSound return sound; } - void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) + void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId) { SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -351,7 +371,7 @@ namespace MWSound } } - void SoundManager::stopSound3D(MWWorld::Ptr ptr) + void SoundManager::stopSound3D(const MWWorld::Ptr &ptr) { SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -398,12 +418,33 @@ namespace MWSound } } - bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const + bool SoundManager::getSoundPlaying(const MWWorld::Ptr &ptr, const std::string& soundId) const { return isPlaying(ptr, soundId); } + void SoundManager::pauseSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= Play_TypeMask; + mOutput->pauseSounds(types); + mPausedSoundTypes |= types; + } + } + + void SoundManager::resumeSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= types&Play_TypeMask&mPausedSoundTypes; + mOutput->resumeSounds(types); + mPausedSoundTypes &= ~types; + } + } + + void SoundManager::updateRegionSound(float duration) { MWWorld::Ptr::CellStore *current = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); @@ -525,24 +566,13 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->second.second != "_say_sound") - { - float basevol = mMasterVolume * mSFXVolume; - float min, max; - lookup(snditer->second.second, basevol, min, max); - snditer->first->mBaseVolume = basevol; - } - else - { - float basevol = mMasterVolume * mVoiceVolume; - snditer->first->mBaseVolume = basevol; - } + snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType()); snditer->first->update(); snditer++; } if(mMusic) { - mMusic->mBaseVolume = mMasterVolume * mMusicVolume; + mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType()); mMusic->update(); } } @@ -577,6 +607,7 @@ namespace MWSound { case SampleType_UInt8: return "U8"; case SampleType_Int16: return "S16"; + case SampleType_Float32: return "Float32"; } return "(unknown sample type)"; } @@ -585,8 +616,11 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: return "Mono"; - case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Mono: return "Mono"; + case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Quad: return "Quad"; + case ChannelConfig_5point1: return "5.1 Surround"; + case ChannelConfig_7point1: return "7.1 Surround"; } return "(unknown channel config)"; } @@ -595,13 +629,17 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: frames *= 1; break; - case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Mono: frames *= 1; break; + case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Quad: frames *= 4; break; + case ChannelConfig_5point1: frames *= 6; break; + case ChannelConfig_7point1: frames *= 8; break; } switch(type) { case SampleType_UInt8: frames *= 1; break; case SampleType_Int16: frames *= 2; break; + case SampleType_Float32: frames *= 4; break; } return frames; } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index a84aa3b9a..2af26d3db 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -14,16 +14,12 @@ #include "../mwbase/soundmanager.hpp" -#include "../mwworld/ptr.hpp" - namespace MWSound { class Sound_Output; struct Sound_Decoder; class Sound; - typedef boost::shared_ptr DecoderPtr; - enum Environment { Env_Normal, Env_Underwater @@ -54,13 +50,17 @@ namespace MWSound Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; + int mPausedSoundTypes; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); - bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + bool isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const; void updateSounds(float duration); void updateRegionSound(float duration); + float volumeFromType(PlayType type) const; + SoundManager(const SoundManager &rhs); SoundManager& operator=(const SoundManager &rhs); @@ -91,7 +91,7 @@ namespace MWSound ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(MWWorld::Ptr reference, const std::string& filename); + virtual void say(const MWWorld::Ptr &reference, const std::string& filename); ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -99,23 +99,26 @@ namespace MWSound ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(MWWorld::Ptr reference=MWWorld::Ptr()) const; + virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const; ///< Is actor not speaking? - virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()); + virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()); ///< Stop an actor speaking - virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); + virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); + ///< Play a 2D audio track, using a custom decoder + + virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound, independently of 3D-position - virtual MWBase::SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal); + virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound from an object - virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); + virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId); ///< Stop the given object from playing the given sound, - virtual void stopSound3D(MWWorld::Ptr reference); + virtual void stopSound3D(const MWWorld::Ptr &reference); ///< Stop the given object from playing all sounds. virtual void stopSound(const MWWorld::CellStore *cell); @@ -124,9 +127,15 @@ namespace MWSound virtual void stopSound(const std::string& soundId); ///< Stop a non-3d looping sound - virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const; + virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask); + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask); + ///< Resumes all previously paused sounds. + virtual void update(float duration); virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index d8c019644..2d257aa61 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -2,6 +2,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include #include "inventorystore.hpp" #include "player.hpp" @@ -15,8 +18,7 @@ namespace MWWorld void ActionEquip::executeImp (const Ptr& actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); // slots that this item can be equipped in std::pair, bool> slots = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); @@ -33,14 +35,67 @@ namespace MWWorld assert(it != invStore.end()); + std::string npcRace = actor.get()->mBase->mRace; + + bool equipped = false; + // equip the item in the first free slot for (std::vector::const_iterator slot=slots.first.begin(); slot!=slots.first.end(); ++slot) { + + // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) + if(npcRace == "argonian" || npcRace == "khajiit") + { + if(*slot == MWWorld::InventoryStore::Slot_Helmet){ + std::vector parts; + + if(it.getType() == MWWorld::ContainerStore::Type_Clothing) + parts = it->get()->mBase->mParts.mParts; + else + parts = it->get()->mBase->mParts.mParts; + + bool allow = true; + for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) + { + if((*itr).mPart == ESM::PRT_Head) + { + if(actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer() ) + MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage13}", std::vector()); + + allow = false; + break; + } + } + + if(!allow) + break; + } + + if (*slot == MWWorld::InventoryStore::Slot_Boots) + { + // Only notify the player, not npcs + if(actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer() ) + { + if(it.getType() == MWWorld::ContainerStore::Type_Clothing){ // It's shoes + MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage15}", std::vector()); + } + + else // It's boots + { + MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage14}", std::vector()); + } + } + break; + } + + } + // if all slots are occupied, replace the last slot if (slot == --slots.first.end()) { invStore.equip(*slot, it); + equipped = true; break; } @@ -48,8 +103,15 @@ namespace MWWorld { // slot is not occupied invStore.equip(*slot, it); + equipped = true; break; } } + + std::string script = MWWorld::Class::get(*it).getScript(*it); + + /* Set OnPCEquip Variable on item's script, if the player is equipping it, and it has a script with that variable declared */ + if(equipped && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && script != "") + (*it).mRefData->getLocals().setVarByInt(script, "onpcequip", 1); } } diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index 040a3856e..728b6e32b 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -10,7 +10,9 @@ namespace MWWorld { - ActionOpen::ActionOpen (const MWWorld::Ptr& container) : Action (false, container) + ActionOpen::ActionOpen (const MWWorld::Ptr& container, bool loot) + : Action (false, container) + , mLoot(loot) { } @@ -20,6 +22,6 @@ namespace MWWorld return; MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); - MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget()); + MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget(), mLoot); } } diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index c49ebefa5..8578995ae 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -13,8 +13,12 @@ namespace MWWorld virtual void executeImp (const MWWorld::Ptr& actor); public: - ActionOpen (const Ptr& container); - ///< \param The Container the Player has activated. + ActionOpen (const Ptr& container, bool loot=false); + ///< \param container The Container the Player has activated. + /// \param loot If true, display the "dispose of corpse" button + + private: + bool mLoot; }; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index fd28dd52e..cdd19b46e 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -14,9 +14,6 @@ namespace MWWorld void ActionTake::executeImp (const Ptr& actor) { - if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return; - // insert into player's inventory MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPtr ("player", true); diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 696a469f7..f95d30df1 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -84,9 +84,9 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS return ptr; } -MWWorld::Cells::Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader) +MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), - mIdCache (20, std::pair ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable + mIdCache (40, std::pair ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable mIdCacheIndex (0) {} @@ -119,6 +119,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) if (result->second.mState!=Ptr::CellStore::State_Loaded) { + // Multiple plugin support for landscape data is much easier than for references. The last plugin wins. result->second.load (mStore, mReader); fillContainers (result->second); } @@ -128,13 +129,14 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) { - std::map::iterator result = mInteriors.find (name); + std::string lowerName = Misc::StringUtils::lowerCase(name); + std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { - const ESM::Cell *cell = mStore.get().find(name); + const ESM::Cell *cell = mStore.get().find(lowerName); - result = mInteriors.insert (std::make_pair (name, Ptr::CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, Ptr::CellStore (cell))).first; } if (result->second.mState!=Ptr::CellStore::State_Loaded) @@ -153,10 +155,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& ce if (cell.mState==Ptr::CellStore::State_Preloaded) { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + std::string lowerCase = Misc::StringUtils::lowerCase(name); if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), lowerCase)) { diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index edaf70055..f999fedde 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -2,6 +2,7 @@ #define GAME_MWWORLD_CELLS_H #include +#include #include #include "ptr.hpp" @@ -19,7 +20,7 @@ namespace MWWorld class Cells { const MWWorld::ESMStore& mStore; - ESM::ESMReader& mReader; + std::vector& mReader; std::map mInteriors; std::map, CellStore> mExteriors; std::vector > mIdCache; @@ -36,7 +37,7 @@ namespace MWWorld public: - Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader); + Cells (const MWWorld::ESMStore& store, std::vector& reader); ///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole /// world diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 1ef2d36a2..baf4fea32 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -10,13 +10,48 @@ namespace MWWorld { + + template + void CellRefList::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) + { + // Get existing reference, in case we need to overwrite it. + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefnum); + + // Skip this when reference was deleted. + // TODO: Support respawning references, in this case, we need to track it somehow. + if (ref.mDeleted) { + mList.erase(iter); + return; + } + + // for throwing exception on unhandled record type + const MWWorld::Store &store = esmStore.get(); + const X *ptr = store.search(ref.mRefID); + + /// \note no longer redundant - changed to Store::search(), don't throw + /// an exception on miss, try to continue (that's how MW does it, anyway) + if (ptr == NULL) { + std::cout << "Warning: could not resolve cell reference " << ref.mRefID << ", trying to continue anyway" << std::endl; + } else { + if (iter != mList.end()) + *iter = LiveRef(ref, ptr); + else + mList.push_back(LiveRef(ref, ptr)); + } + } + + template bool operator==(const LiveCellRef& ref, int pRefnum) + { + return (ref.mRef.mRefnum == pRefnum); + } + CellStore::CellStore (const ESM::Cell *cell) : mCell (cell), mState (State_Unloaded) { mWaterLevel = cell->mWater; } - void CellStore::load (const MWWorld::ESMStore &store, ESM::ESMReader &esm) + void CellStore::load (const MWWorld::ESMStore &store, std::vector &esm) { if (mState!=State_Loaded) { @@ -31,7 +66,7 @@ namespace MWWorld } } - void CellStore::preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm) + void CellStore::preload (const MWWorld::ESMStore &store, std::vector &esm) { if (mState==State_Unloaded) { @@ -41,63 +76,75 @@ namespace MWWorld } } - void CellStore::listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm) + void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector &esm) { assert (mCell); - if (mCell->mContext.filename.empty()) + if (mCell->mContextList.size() == 0) return; // this is a dynamically generated cell -> skipping. - // Reopen the ESM reader and seek to the right position. - mCell->restore (esm); - - ESM::CellRef ref; - - // Get each reference in turn - while (mCell->getNextRef (esm, ref)) + // Load references from all plugins that do something with this cell. + for (size_t i = 0; i < mCell->mContextList.size(); i++) { - std::string lowerCase; + // Reopen the ESM reader and seek to the right position. + int index = mCell->mContextList.at(i).index; + mCell->restore (esm[index], i); - std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + ESM::CellRef ref; - mIds.push_back (lowerCase); + // Get each reference in turn + while (mCell->getNextRef (esm[index], ref)) + { + std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID); + if (ref.mDeleted) { + // Right now, don't do anything. Where is "listRefs" actually used, anyway? + // Skipping for now... + continue; + } + + mIds.push_back (lowerCase); + } } std::sort (mIds.begin(), mIds.end()); } - void CellStore::loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm) + void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) { assert (mCell); - if (mCell->mContext.filename.empty()) + if (mCell->mContextList.size() == 0) return; // this is a dynamically generated cell -> skipping. - // Reopen the ESM reader and seek to the right position. - mCell->restore(esm); - - ESM::CellRef ref; - - // Get each reference in turn - while(mCell->getNextRef(esm, ref)) + // Load references from all plugins that do something with this cell. + for (size_t i = 0; i < mCell->mContextList.size(); i++) { - std::string lowerCase; + // Reopen the ESM reader and seek to the right position. + int index = mCell->mContextList.at(i).index; + mCell->restore (esm[index], i); - std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + ESM::CellRef ref; - int rec = store.find(ref.mRefID); - - ref.mRefID = lowerCase; - - /* We can optimize this further by storing the pointer to the - record itself in store.all, so that we don't need to look it - up again here. However, never optimize. There are infinite - opportunities to do that later. - */ - switch(rec) + // Get each reference in turn + while(mCell->getNextRef(esm[index], ref)) { + // Don't load reference if it was moved to a different cell. + std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID); + ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefnum); + if (iter != mCell->mMovedRefs.end()) { + continue; + } + int rec = store.find(ref.mRefID); + + ref.mRefID = lowerCase; + + /* We can optimize this further by storing the pointer to the + record itself in store.all, so that we don't need to look it + up again here. However, never optimize. There are infinite + opportunities to do that later. + */ + switch(rec) + { case ESM::REC_ACTI: mActivators.load(ref, store); break; case ESM::REC_ALCH: mPotions.load(ref, store); break; case ESM::REC_APPA: mAppas.load(ref, store); break; @@ -119,10 +166,62 @@ namespace MWWorld case ESM::REC_STAT: mStatics.load(ref, store); break; case ESM::REC_WEAP: mWeapons.load(ref, store); break; - case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; - default: - std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; + default: + std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + } } } + + // Load moved references, from separately tracked list. + for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); it++) + { + // Doesn't seem to work in one line... huh? Too sleepy to check... + ESM::CellRef &ref = const_cast(*it); + //ESM::CellRef &ref = const_cast(it->second); + + std::string lowerCase; + + std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + int rec = store.find(ref.mRefID); + + ref.mRefID = lowerCase; + + /* We can optimize this further by storing the pointer to the + record itself in store.all, so that we don't need to look it + up again here. However, never optimize. There are infinite + opportunities to do that later. + */ + switch(rec) + { + case ESM::REC_ACTI: mActivators.load(ref, store); break; + case ESM::REC_ALCH: mPotions.load(ref, store); break; + case ESM::REC_APPA: mAppas.load(ref, store); break; + case ESM::REC_ARMO: mArmors.load(ref, store); break; + case ESM::REC_BOOK: mBooks.load(ref, store); break; + case ESM::REC_CLOT: mClothes.load(ref, store); break; + case ESM::REC_CONT: mContainers.load(ref, store); break; + case ESM::REC_CREA: mCreatures.load(ref, store); break; + case ESM::REC_DOOR: mDoors.load(ref, store); break; + case ESM::REC_INGR: mIngreds.load(ref, store); break; + case ESM::REC_LEVC: mCreatureLists.load(ref, store); break; + case ESM::REC_LEVI: mItemLists.load(ref, store); break; + case ESM::REC_LIGH: mLights.load(ref, store); break; + case ESM::REC_LOCK: mLockpicks.load(ref, store); break; + case ESM::REC_MISC: mMiscItems.load(ref, store); break; + case ESM::REC_NPC_: mNpcs.load(ref, store); break; + case ESM::REC_PROB: mProbes.load(ref, store); break; + case ESM::REC_REPA: mRepairs.load(ref, store); break; + case ESM::REC_STAT: mStatics.load(ref, store); break; + case ESM::REC_WEAP: mWeapons.load(ref, store); break; + + case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; + default: + std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + } + + } } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index ba3d24d7e..c182f196b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -3,17 +3,18 @@ #include -#include +#include #include #include "refdata.hpp" #include "esmstore.hpp" +struct C; namespace MWWorld { class Ptr; class ESMStore; - + /// A reference to one object (of any type) in a cell. /// /// Constructing this with a CellRef instance in the constructor means that @@ -42,6 +43,8 @@ namespace MWWorld /// runtime-data RefData mData; }; + + template bool operator==(const LiveCellRef& ref, int pRefnum); /// A list of cell references template @@ -51,21 +54,14 @@ namespace MWWorld typedef std::list List; List mList; - /// Searches for reference of appropriate type in given ESMStore. - /// If reference exists, loads it into container, throws an exception - /// on miss - void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) - { - // for throwing exception on unhandled record type - const MWWorld::Store &store = esmStore.get(); - const X *ptr = store.find(ref.mRefID); - - /// \note redundant because Store::find() throws exception on miss - if (ptr == NULL) { - throw std::runtime_error("Error resolving cell reference " + ref.mRefID); - } - mList.push_back(LiveRef(ref, ptr)); - } + // Search for the given reference in the given reclist from + // ESMStore. Insert the reference into the list if a match is + // found. If not, throw an exception. + // Moved to cpp file, as we require a custom compare operator for it, + // and the build will fail with an ugly three-way cyclic header dependence + // so we need to pass the instantiation of the method to the lnker, when + // all methods are known. + void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore); LiveRef *find (const std::string& name) { @@ -124,9 +120,9 @@ namespace MWWorld CellRefList mStatics; CellRefList mWeapons; - void load (const MWWorld::ESMStore &store, ESM::ESMReader &esm); + void load (const MWWorld::ESMStore &store, std::vector &esm); - void preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm); + void preload (const MWWorld::ESMStore &store, std::vector &esm); /// Call functor (ref) for each reference. functor must return a bool. Returning /// false will abort the iteration. @@ -185,9 +181,9 @@ namespace MWWorld } /// Run through references and store IDs - void listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm); + void listRefs(const MWWorld::ESMStore &store, std::vector &esm); - void loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm); + void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c76019149..71b24b65d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -122,6 +122,11 @@ namespace MWWorld return 0; } + float Class::getJump (const Ptr& ptr) const + { + return 0; + } + MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const { throw std::runtime_error ("movement settings not supported by class"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 07dcb8fe0..1a6a16ca0 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -140,6 +140,9 @@ namespace MWWorld virtual float getSpeed (const Ptr& ptr) const; ///< Return movement speed. + virtual float getJump(const MWWorld::Ptr &ptr) const; + ///< Return jump velocity (not accounting for movement) + virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; ///< Return desired movement. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index e47f2191a..8a7884e9e 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -8,13 +8,17 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/scriptmanager.hpp" #include "manualref.hpp" #include "refdata.hpp" #include "class.hpp" +#include "localscripts.hpp" +#include "player.hpp" namespace { @@ -37,7 +41,7 @@ namespace bool compare_string_ci(std::string str1, std::string str2) { - boost::algorithm::to_lower(str1); + Misc::StringUtils::toLower(str1); return str1 == str2; } } @@ -71,6 +75,36 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& ptr) +{ + MWWorld::ContainerStoreIterator it = addImp(ptr); + MWWorld::Ptr item = *it; + + std::string script = MWWorld::Class::get(item).getScript(item); + if(script != "") + { + CellStore *cell; + + Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); + + if(&(MWWorld::Class::get (player).getContainerStore (player)) == this) + { + cell = 0; // Items in player's inventory have cell set to 0, so their scripts will never be removed + + // Set OnPCAdd special variable, if it is declared + item.mRefData->getLocals().setVarByInt(script, "onpcadd", 1); + } + else + cell = player.getCell(); + + item.mCell = cell; + item.mContainerStore = 0; + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item); + } + + return it; +} + +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) { int type = getType(ptr); @@ -162,7 +196,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const MWWor } ref.getPtr().getRefData().setCount (std::abs(iter->mCount)); /// \todo implement item restocking (indicated by negative count) - add (ref.getPtr()); + addImp (ref.getPtr()); } flagAsModified(); @@ -514,7 +548,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStoreIterator::operator++ (int bool MWWorld::ContainerStoreIterator::isEqual (const ContainerStoreIterator& iter) const { - assert (mContainer==iter.mContainer); + if (mContainer!=iter.mContainer) + return false; if (mType!=iter.mType) return false; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 1297fc53c..e4f75d547 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -52,6 +52,7 @@ namespace MWWorld int mStateId; mutable float mCachedWeight; mutable bool mWeightUpToDate; + ContainerStoreIterator addImp (const Ptr& ptr); public: diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 73f5185c9..257676076 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -3,6 +3,8 @@ #include #include +#include + namespace MWWorld { @@ -25,6 +27,33 @@ void ESMStore::load(ESM::ESMReader &esm) ESM::Dialogue *dialogue = 0; + // Cache parent esX files by tracking their indices in the global list of + // all files/readers used by the engine. This will greaty accelerate + // refnumber mangling, as required for handling moved references. + int index = ~0; + const ESM::ESMReader::MasterList &masters = esm.getMasters(); + std::vector *allPlugins = esm.getGlobalReaderList(); + for (size_t j = 0; j < masters.size(); j++) { + ESM::MasterData &mast = const_cast(masters[j]); + std::string fname = mast.name; + for (int i = 0; i < esm.getIndex(); i++) { + const std::string &candidate = allPlugins->at(i).getContext().filename; + std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); + if (fname == fnamecandidate) { + index = i; + break; + } + } + if (index == (int)~0) { + // Tried to load a parent file that has not been loaded yet. This is bad, + // the launcher should have taken care of this. + std::string fstring = "File " + fname + " asks for parent file " + masters[j].name + + ", but it has not been loaded yet. Please check your load order."; + esm.fail(fstring); + } + mast.index = index; + } + // Loop through all records while(esm.hasMoreRecs()) { @@ -55,12 +84,21 @@ void ESMStore::load(ESM::ESMReader &esm) } else { // Load it std::string id = esm.getHNOString("NAME"); + // ... unless it got deleted! This means that the following record + // has been deleted, and trying to load it using standard assumptions + // on the structure will (probably) fail. + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + it->second->eraseStatic(id); + continue; + } it->second->load(esm, id); if (n.val==ESM::REC_DIAL) { // dirty hack, but it is better than non-const search() // or friends - dialogue = &mDialogs.mStatic.back(); + //dialogue = &mDialogs.mStatic.back(); + dialogue = const_cast(mDialogs.find(id)); assert (dialogue->mId == id); } else { dialogue = 0; @@ -84,7 +122,6 @@ void ESMStore::load(ESM::ESMReader &esm) cout << *it << " "; cout << endl; */ - setUp(); } void ESMStore::setUp() @@ -100,12 +137,11 @@ void ESMStore::setUp() ESM::NPC item; item.mId = "player"; - std::vector::iterator pIt = - std::lower_bound(mNpcs.mStatic.begin(), mNpcs.mStatic.end(), item, RecordCmp()); - assert(pIt != mNpcs.mStatic.end() && pIt->mId == "player"); + const ESM::NPC *pIt = mNpcs.find("player"); + assert(pIt != NULL); mNpcs.insert(*pIt); - mNpcs.mStatic.erase(pIt); + mNpcs.eraseStatic(pIt->mId); } } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 9917254ee..b9b95e4f4 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -6,7 +6,7 @@ #include #include "store.hpp" -namespace MWWorld +namespace MWWorld { class ESMStore { @@ -94,6 +94,9 @@ namespace MWWorld ESMStore() : mDynamicCount(0) { + // Cell store needs access to this for tracking moved references + mCells.mEsmStore = this; + mStores[ESM::REC_ACTI] = &mActivators; mStores[ESM::REC_ALCH] = &mPotions; mStores[ESM::REC_APPA] = &mAppas; @@ -158,7 +161,7 @@ namespace MWWorld std::ostringstream id; id << "$dynamic" << mDynamicCount++; record.mId = id.str(); - + T *ptr = store.insert(record); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { if (it->second == &store) { @@ -168,7 +171,8 @@ namespace MWWorld return ptr; } - private: + // This method must be called once, after loading all master/plugin files. This can only be done + // from the outside, so it must be public. void setUp(); }; @@ -179,7 +183,7 @@ namespace MWWorld template <> inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) { - if (StringUtils::ciEqual(npc.mId, "player")) { + if (Misc::StringUtils::ciEqual(npc.mId, "player")) { return mNpcs.insert(npc); } else if (mNpcs.search(npc.mId) != 0) { std::ostringstream msg; @@ -191,7 +195,7 @@ namespace MWWorld std::ostringstream id; id << "$dynamic" << mDynamicCount++; record.mId = id.str(); - + ESM::NPC *ptr = mNpcs.insert(record); mIds[ptr->mId] = ESM::REC_NPC_; return ptr; diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 76dede5a3..9e57910ee 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -7,24 +7,35 @@ namespace MWWorld { + std::vector Globals::getGlobals () const + { + std::vector retval; + Collection::const_iterator it; + for(it = mVariables.begin(); it != mVariables.end(); ++it){ + retval.push_back(it->first); + } + + return retval; + } + Globals::Collection::const_iterator Globals::find (const std::string& name) const { Collection::const_iterator iter = mVariables.find (name); - + if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); - - return iter; + + return iter; } Globals::Collection::iterator Globals::find (const std::string& name) { Collection::iterator iter = mVariables.find (name); - + if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); - - return iter; + + return iter; } Globals::Globals (const MWWorld::ESMStore& store) @@ -35,44 +46,41 @@ namespace MWWorld { char type = ' '; Data value; - - switch (iter->mType) + + switch (iter->mValue.getType()) { case ESM::VT_Short: - + type = 's'; - value.mShort = *reinterpret_cast ( - &iter->mValue); + value.mShort = iter->mValue.getInteger(); break; - - case ESM::VT_Int: - + + case ESM::VT_Long: + type = 'l'; - value.mLong = *reinterpret_cast ( - &iter->mValue); + value.mLong = iter->mValue.getInteger(); break; - + case ESM::VT_Float: - + type = 'f'; - value.mFloat = *reinterpret_cast ( - &iter->mValue); + value.mFloat = iter->mValue.getFloat(); break; - + default: - + throw std::runtime_error ("unsupported global variable type"); - } + } mVariables.insert (std::make_pair (iter->mId, std::make_pair (type, value))); } - + if (mVariables.find ("dayspassed")==mVariables.end()) { // vanilla Morrowind does not define dayspassed. Data value; value.mLong = 0; - + mVariables.insert (std::make_pair ("dayspassed", std::make_pair ('l', value))); } } @@ -80,31 +88,31 @@ namespace MWWorld const Globals::Data& Globals::operator[] (const std::string& name) const { Collection::const_iterator iter = find (name); - + return iter->second.second; } Globals::Data& Globals::operator[] (const std::string& name) { Collection::iterator iter = find (name); - + return iter->second.second; } - + void Globals::setInt (const std::string& name, int value) { Collection::iterator iter = find (name); - + switch (iter->second.first) { case 's': iter->second.second.mShort = value; break; case 'l': iter->second.second.mLong = value; break; case 'f': iter->second.second.mFloat = value; break; - + default: throw std::runtime_error ("unsupported global variable type"); } } - + void Globals::setFloat (const std::string& name, float value) { Collection::iterator iter = find (name); @@ -116,9 +124,9 @@ namespace MWWorld case 'f': iter->second.second.mFloat = value; break; default: throw std::runtime_error ("unsupported global variable type"); - } + } } - + int Globals::getInt (const std::string& name) const { Collection::const_iterator iter = find (name); @@ -130,13 +138,13 @@ namespace MWWorld case 'f': return iter->second.second.mFloat; default: throw std::runtime_error ("unsupported global variable type"); - } + } } - + float Globals::getFloat (const std::string& name) const { Collection::const_iterator iter = find (name); - + switch (iter->second.first) { case 's': return iter->second.second.mShort; @@ -144,16 +152,16 @@ namespace MWWorld case 'f': return iter->second.second.mFloat; default: throw std::runtime_error ("unsupported global variable type"); - } + } } - + char Globals::getType (const std::string& name) const { Collection::const_iterator iter = mVariables.find (name); - + if (iter==mVariables.end()) return ' '; - + return iter->second.first; } } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index c7aee5f93..681bd560e 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWWORLD_GLOBALS_H #define GAME_MWWORLD_GLOBALS_H +#include #include #include @@ -53,6 +54,8 @@ namespace MWWorld char getType (const std::string& name) const; ///< If there is no global variable with this name, ' ' is returned. + + std::vector getGlobals () const; }; } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index a821ad486..5ec5ca9b5 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -3,6 +3,10 @@ #include "esmstore.hpp" #include "cellstore.hpp" +#include "class.hpp" +#include "containerstore.hpp" + + namespace { template @@ -19,6 +23,32 @@ namespace } } } + + // Adds scripts for items in containers (containers/npcs/creatures) + template + void listCellScriptsCont (MWWorld::LocalScripts& localScripts, + MWWorld::CellRefList& cellRefList, MWWorld::Ptr::CellStore *cell) + { + for (typename MWWorld::CellRefList::List::iterator iter ( + cellRefList.mList.begin()); + iter!=cellRefList.mList.end(); ++iter) + { + + MWWorld::Ptr containerPtr (&*iter, cell); + + MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr); + for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) + { + std::string script = MWWorld::Class::get(*it3).getScript(*it3); + if(script != "") + { + MWWorld::Ptr item = *it3; + item.mCell = cell; + localScripts.add (script, item); + } + } + } + } } MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {} @@ -78,13 +108,16 @@ void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell) listCellScripts (*this, cell->mBooks, cell); listCellScripts (*this, cell->mClothes, cell); listCellScripts (*this, cell->mContainers, cell); + listCellScriptsCont (*this, cell->mContainers, cell); listCellScripts (*this, cell->mCreatures, cell); + listCellScriptsCont (*this, cell->mCreatures, cell); listCellScripts (*this, cell->mDoors, cell); listCellScripts (*this, cell->mIngreds, cell); listCellScripts (*this, cell->mLights, cell); listCellScripts (*this, cell->mLockpicks, cell); listCellScripts (*this, cell->mMiscItems, cell); listCellScripts (*this, cell->mNpcs, cell); + listCellScriptsCont (*this, cell->mNpcs, cell); listCellScripts (*this, cell->mProbes, cell); listCellScripts (*this, cell->mRepairs, cell); listCellScripts (*this, cell->mWeapons, cell); @@ -101,7 +134,7 @@ void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell) while (iter!=mScripts.end()) { - if (iter->second.getCell()==cell) + if (iter->second.mCell==cell) { if (iter==mIter) ++mIter; @@ -113,6 +146,20 @@ void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell) } } +void MWWorld::LocalScripts::remove (RefData *ref) +{ + for (std::list >::iterator iter = mScripts.begin(); + iter!=mScripts.end(); ++iter) + if (&(iter->second.getRefData()) == ref) + { + if (iter==mIter) + ++mIter; + + mScripts.erase (iter); + break; + } +} + void MWWorld::LocalScripts::remove (const Ptr& ptr) { for (std::list >::iterator iter = mScripts.begin(); diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 028dcdeda..840243fff 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -10,6 +10,7 @@ namespace MWWorld { struct ESMStore; class CellStore; + class RefData; /// \brief List of active local scripts class LocalScripts @@ -47,6 +48,8 @@ namespace MWWorld void clearCell (CellStore *cell); ///< Remove all scripts belonging to \a cell. + + void remove (RefData *ref); void remove (const Ptr& ptr); ///< Remove script for given reference (ignored if reference does not have a scirpt listed). diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 5359c4eb2..895517481 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -9,7 +9,11 @@ #include #include -#include +#include +#include +#include + +#include //#include "../mwbase/world.hpp" // FIXME #include "../mwbase/environment.hpp" @@ -21,29 +25,196 @@ using namespace Ogre; namespace MWWorld { - PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : - mRender(_rend), mEngine(0), mFreeFly (true) - { + static const float sMaxSlope = 60.0f; + static const float sStepSize = 30.0f; + // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. + static const int sMaxIterations = 50; - playerphysics = new playerMove; + class MovementSolver + { + private: + static bool stepMove(Ogre::Vector3& position, const Ogre::Vector3 &velocity, float remainingTime, + const Ogre::Vector3 &halfExtents, bool isInterior, + OEngine::Physic::PhysicEngine *engine) + { + traceResults trace; // no initialization needed + + newtrace(&trace, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), + halfExtents, isInterior, engine); + if(trace.fraction == 0.0f) + return false; + + newtrace(&trace, trace.endpos, trace.endpos + velocity*remainingTime, + halfExtents, isInterior, engine); + if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) + return false; + + newtrace(&trace, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); + if(getSlope(trace.planenormal) <= sMaxSlope) + { + // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. + position = trace.endpos; + return true; + } + + return false; + } + + static void clipVelocity(Ogre::Vector3& inout, const Ogre::Vector3& normal, float overbounce=1.0f) + { + //Math stuff. Basically just project the velocity vector onto the plane represented by the normal. + //More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity. + float backoff = inout.dotProduct(normal); + if(backoff < 0.0f) + backoff *= overbounce; + else + backoff /= overbounce; + + inout -= normal*backoff; + } + + static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction) + { + Ogre::Vector3 normalizedDirection(direction); + normalizedDirection.normalise(); + + // no divide by normalizedDirection.length necessary because it's normalized + velocity = normalizedDirection * velocity.dotProduct(normalizedDirection); + } + + static float getSlope(const Ogre::Vector3 &normal) + { + return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); + } + + public: + static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, + bool gravity, OEngine::Physic::PhysicEngine *engine) + { + const ESM::Position &refpos = ptr.getRefData().getPosition(); + Ogre::Vector3 position(refpos.pos); + + /* Anything to collide with? */ + OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); + if(!physicActor || !physicActor->getCollisionMode()) + { + // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? + return position + (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( -refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + movement; + } + + traceResults trace; //no initialization needed + bool onground = false; + float remainingTime = time; + bool isInterior = !ptr.getCell()->isExterior(); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); + physicActor->enableCollisions(false); + + Ogre::Vector3 velocity; + if(!gravity) + { + velocity = (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( -refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + movement / time; + } + else + { + if(!(movement.z > 0.0f)) + { + newtrace(&trace, position, position-Ogre::Vector3(0,0,4), halfExtents, isInterior, engine); + if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) + onground = true; + } + velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*movement / time; + velocity.z += physicActor->getVerticalForce(); + } + + Ogre::Vector3 clippedVelocity(velocity); + if(onground) + { + // if we're on the ground, force velocity to track it + clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z); + clipVelocity(clippedVelocity, trace.planenormal); + } + + const Ogre::Vector3 up(0.0f, 0.0f, 1.0f); + Ogre::Vector3 newPosition = position; + int iterations = 0; + do { + // trace to where character would go if there were no obstructions + newtrace(&trace, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine); + newPosition = trace.endpos; + remainingTime = remainingTime * (1.0f-trace.fraction); + + // check for obstructions + if(trace.fraction < 1.0f) + { + //std::cout<<"angle: "< 0.0f); + + if(onground) + { + newtrace(&trace, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+4.0f), halfExtents, isInterior, engine); + if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) + newPosition.z = trace.endpos.z + 2.0f; + else + onground = false; + } + physicActor->setOnGround(onground); + physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f); + physicActor->enableCollisions(true); + return newPosition; + } + }; + + + PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : + mRender(_rend), mEngine(0) + { // Create physics. shapeLoader is deleted by the physic engine NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); - playerphysics->mEngine = mEngine; } PhysicsSystem::~PhysicsSystem() { delete mEngine; - delete playerphysics; - } + OEngine::Physic::PhysicEngine* PhysicsSystem::getEngine() { return mEngine; } - std::pair PhysicsSystem::getFacedHandle (MWWorld::World& world) + std::pair PhysicsSystem::getFacedHandle (MWWorld::World& world, float queryDistance) { btVector3 dir(0, 1, 0); dir = dir.rotate(btVector3(1, 0, 0), mPlayerData.pitch); @@ -56,11 +227,14 @@ namespace MWWorld mPlayerData.eyepos.z); origin += dir * 5; - btVector3 dest = origin + dir * 500; - return mEngine->rayTest(origin, dest); + btVector3 dest = origin + dir * queryDistance; + std::pair result; + /*auto*/ result = mEngine->rayTest(origin, dest); + result.second *= queryDistance; + return std::make_pair (result.second, result.first); } - std::vector < std::pair > PhysicsSystem::getFacedObjects () + std::vector < std::pair > PhysicsSystem::getFacedHandles (float queryDistance) { btVector3 dir(0, 1, 0); dir = dir.rotate(btVector3(1, 0, 0), mPlayerData.pitch); @@ -73,35 +247,36 @@ namespace MWWorld mPlayerData.eyepos.z); origin += dir * 5; - btVector3 dest = origin + dir * 500; - return mEngine->rayTest2(origin, dest); + btVector3 dest = origin + dir * queryDistance; + std::vector < std::pair > results; + /* auto */ results = mEngine->rayTest2(origin, dest); + std::vector < std::pair >::iterator i; + for (/* auto */ i = results.begin (); i != results.end (); ++i) + i->first *= queryDistance; + return results; } - std::vector < std::pair > PhysicsSystem::getFacedObjects (float mouseX, float mouseY) + std::vector < std::pair > PhysicsSystem::getFacedHandles (float mouseX, float mouseY, float queryDistance) { Ray ray = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); Ogre::Vector3 from = ray.getOrigin(); - Ogre::Vector3 to = ray.getPoint(500); /// \todo make this distance (ray length) configurable + Ogre::Vector3 to = ray.getPoint(queryDistance); btVector3 _from, _to; - // OGRE to MW coordinates - _from = btVector3(from.x, -from.z, from.y); - _to = btVector3(to.x, -to.z, to.y); + _from = btVector3(from.x, from.y, from.z); + _to = btVector3(to.x, to.y, to.z); - return mEngine->rayTest2(_from,_to); + std::vector < std::pair > results; + /* auto */ results = mEngine->rayTest2(_from,_to); + std::vector < std::pair >::iterator i; + for (/* auto */ i = results.begin (); i != results.end (); ++i) + i->first *= queryDistance; + return results; } void PhysicsSystem::setCurrentWater(bool hasWater, int waterHeight) { - playerphysics->hasWater = hasWater; - if(hasWater){ - playerphysics->waterHeight = waterHeight; - } - for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) - { - it->second->setCurrentWater(hasWater, waterHeight); - } - + // TODO: store and use } btVector3 PhysicsSystem::getRayPoint(float extent) @@ -110,7 +285,7 @@ namespace MWWorld Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); /// \todo make this distance (ray length) configurable + btVector3 result(centerRay.getPoint(extent).x,centerRay.getPoint(extent).y,centerRay.getPoint(extent).z); return result; } @@ -118,7 +293,7 @@ namespace MWWorld { //get a ray pointing to the center of the viewport Ray centerRay = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); - btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); /// \todo make this distance (ray length) configurable + btVector3 result(centerRay.getPoint(extent).x,centerRay.getPoint(extent).y,centerRay.getPoint(extent).z); return result; } @@ -158,9 +333,8 @@ namespace MWWorld Ogre::Vector3 to = ray.getPoint(200); /// \todo make this distance (ray length) configurable btVector3 _from, _to; - // OGRE to MW coordinates - _from = btVector3(from.x, -from.z, from.y); - _to = btVector3(to.x, -to.z, to.y); + _from = btVector3(from.x, from.y, from.z); + _to = btVector3(to.x, to.y, to.z); std::pair result = mEngine->rayTest(_from, _to); @@ -172,71 +346,11 @@ namespace MWWorld } } - void PhysicsSystem::doPhysics(float dt, const std::vector >& actors) + Ogre::Vector3 PhysicsSystem::move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, bool gravity) { - //set the DebugRenderingMode. To disable it,set it to 0 - //eng->setDebugRenderingMode(1); - - //set the movement keys to 0 (no movement) for every actor) - for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) - { - OEngine::Physic::PhysicActor* act = it->second; - act->setMovement(0,0,0); - } - - playerMove::playercmd& pm_ref = playerphysics->cmd; - - - pm_ref.rightmove = 0; - pm_ref.forwardmove = 0; - pm_ref.upmove = 0; - - - //playerphysics->ps.move_type = PM_NOCLIP; - for (std::vector >::const_iterator iter (actors.begin()); - iter!=actors.end(); ++iter) - { - //dirty stuff to get the camera orientation. Must be changed! - if (iter->first == "player") { - playerphysics->ps.viewangles.x = - Ogre::Radian(mPlayerData.pitch).valueDegrees(); - - - - playerphysics->ps.viewangles.y = - Ogre::Radian(mPlayerData.yaw).valueDegrees() + 90; - - pm_ref.rightmove = iter->second.x; - pm_ref.forwardmove = -iter->second.y; - pm_ref.upmove = iter->second.z; - } - } - mEngine->stepSimulation(dt); + return MovementSolver::move(ptr, movement, time, gravity, mEngine); } - std::vector< std::pair > PhysicsSystem::doPhysicsFixed ( - const std::vector >& actors) - { - Pmove(playerphysics); - - - std::vector< std::pair > response; - for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) - { - - Ogre::Vector3 coord = it->second->getPosition(); - if(it->first == "player"){ - - coord = playerphysics->ps.origin ; - - } - - - response.push_back(std::pair(it->first, coord)); - } - - return response; - } void PhysicsSystem::addHeightField (float* heights, int x, int y, float yoffset, @@ -278,46 +392,20 @@ namespace MWWorld void PhysicsSystem::moveObject (const Ptr& ptr) { - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - std::string handle = node->getName(); - Ogre::Vector3 position = node->getPosition(); - if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) - { - // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow - // start positions others than 0, 0, 0 - - - if(dynamic_cast(body->getCollisionShape()) == NULL){ - btTransform tr = body->getWorldTransform(); - tr.setOrigin(btVector3(position.x,position.y,position.z)); - body->setWorldTransform(tr); - } - else{ - //For objects that contain a box shape. - //Do any such objects exist? Perhaps animated objects? - mEngine->boxAdjustExternal(handleToMesh[handle], body, node->getScale().x, position, node->getOrientation()); - } - } - if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) - { - // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow - // start positions others than 0, 0, 0 - if (handle == "player") - { - playerphysics->ps.origin = position; - } - else - { - act->setPosition(position); - } - } + Ogre::SceneNode *node = ptr.getRefData().getBaseNode(); + const std::string &handle = node->getName(); + const Ogre::Vector3 &position = node->getPosition(); + if(OEngine::Physic::RigidBody *body = mEngine->getRigidBody(handle)) + body->getWorldTransform().setOrigin(btVector3(position.x,position.y,position.z)); + else if(OEngine::Physic::PhysicActor *physact = mEngine->getCharacter(handle)) + physact->setPosition(position); } void PhysicsSystem::rotateObject (const Ptr& ptr) { Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - std::string handle = node->getName(); - Ogre::Quaternion rotation = node->getOrientation(); + const std::string &handle = node->getName(); + const Ogre::Quaternion &rotation = node->getOrientation(); if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { //Needs to be changed @@ -335,7 +423,7 @@ namespace MWWorld void PhysicsSystem::scaleObject (const Ptr& ptr) { Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - std::string handle = node->getName(); + const std::string &handle = node->getName(); if(handleToMesh.find(handle) != handleToMesh.end()) { removeObject(handle); @@ -348,7 +436,6 @@ namespace MWWorld bool PhysicsSystem::toggleCollisionMode() { - playerphysics->ps.move_type = (playerphysics->ps.move_type == PM_NOCLIP ? PM_NORMAL : PM_NOCLIP); for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { if (it->first=="player") @@ -359,12 +446,10 @@ namespace MWWorld if(cmode) { act->enableCollisions(false); - mFreeFly = true; return false; } else { - mFreeFly = false; act->enableCollisions(true); return true; } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 8bd73fd6c..60c8246ae 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -1,12 +1,27 @@ #ifndef GAME_MWWORLD_PHYSICSSYSTEM_H #define GAME_MWWORLD_PHYSICSSYSTEM_H -#include -#include "ptr.hpp" -#include +#include + +#include + + +namespace OEngine +{ + namespace Render + { + class OgreRenderer; + } + namespace Physic + { + class PhysicEngine; + } +} namespace MWWorld { + class World; + class Ptr; class PhysicsSystem { @@ -14,12 +29,6 @@ namespace MWWorld PhysicsSystem (OEngine::Render::OgreRenderer &_rend); ~PhysicsSystem (); - void doPhysics(float duration, const std::vector >& actors); - ///< do physics with dt - Usage: first call doPhysics with frame dt, then call doPhysicsFixed as often as time steps have passed - - std::vector< std::pair > doPhysicsFixed (const std::vector >& actors); - ///< do physics with fixed timestep - Usage: first call doPhysics with frame dt, then call doPhysicsFixed as often as time steps have passed - void addObject (const MWWorld::Ptr& ptr); void addActor (const MWWorld::Ptr& ptr); @@ -41,14 +50,15 @@ namespace MWWorld bool toggleCollisionMode(); - std::pair getFacedHandle (MWWorld::World& world); + Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, bool gravity); + + std::pair getFacedHandle (MWWorld::World& world, float queryDistance); + std::vector < std::pair > getFacedHandles (float queryDistance); + std::vector < std::pair > getFacedHandles (float mouseX, float mouseY, float queryDistance); btVector3 getRayPoint(float extent); btVector3 getRayPoint(float extent, float mouseX, float mouseY); - std::vector < std::pair > getFacedObjects (); - - std::vector < std::pair > getFacedObjects (float mouseX, float mouseY); // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); @@ -75,8 +85,6 @@ namespace MWWorld OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; - bool mFreeFly; - playerMove* playerphysics; std::map handleToMesh; PhysicsSystem (const PhysicsSystem&); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 3414ba448..03dd1abc7 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -71,6 +71,12 @@ namespace MWWorld MWWorld::Class::get (ptr).getMovementSettings (ptr).mUpDown = value; } + void Player::setRunState(bool run) + { + MWWorld::Ptr ptr = getPlayer(); + MWWorld::Class::get(ptr).setStance(ptr, MWWorld::Class::Run, run); + } + void Player::toggleRunning() { MWWorld::Ptr ptr = getPlayer(); @@ -80,6 +86,13 @@ namespace MWWorld MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Run, !running); } + void Player::setSneak(bool sneak) + { + MWWorld::Ptr ptr = getPlayer(); + + MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Sneak, sneak); + } + MWMechanics::DrawState_ Player::getDrawState() { MWWorld::Ptr ptr = getPlayer(); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 1c1ef76cf..5f2fc3a08 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -65,7 +65,9 @@ namespace MWWorld void setForwardBackward (int value); void setUpDown(int value); + void setRunState(bool run); void toggleRunning(); + void setSneak(bool sneak); }; } #endif diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 594ddef2d..d97ebcc6e 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -74,7 +74,7 @@ namespace MWWorld bool isInCell() const { - return (mCell != 0); + return (mContainerStore == 0); } void setContainerStore (ContainerStore *store); diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp index 0b1655100..7de4f5565 100644 --- a/apps/openmw/mwworld/recordcmp.hpp +++ b/apps/openmw/mwworld/recordcmp.hpp @@ -1,62 +1,12 @@ #ifndef OPENMW_MWWORLD_RECORDCMP_H #define OPENMW_MWWORLD_RECORDCMP_H -#include #include -#include #include namespace MWWorld { - /// \todo move this to another location - class StringUtils - { - struct ci - { - bool operator()(int x, int y) const { - return std::tolower(x) < std::tolower(y); - } - }; - - public: - static bool ciLess(const std::string &x, const std::string &y) { - return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); - } - - static bool ciEqual(const std::string &x, const std::string &y) { - if (x.size() != y.size()) { - return false; - } - std::string::const_iterator xit = x.begin(); - std::string::const_iterator yit = y.begin(); - for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit) != std::tolower(*yit)) { - return false; - } - } - return true; - } - - /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { - std::transform( - inout.begin(), - inout.end(), - inout.begin(), - (int (*)(int)) std::tolower - ); - return inout; - } - - /// Returns lower case copy of input string - static std::string lowerCase(const std::string &in) - { - std::string out = in; - return toLower(out); - } - }; - struct RecordCmp { template @@ -67,17 +17,17 @@ namespace MWWorld template <> inline bool RecordCmp::operator()(const ESM::Dialogue &x, const ESM::Dialogue &y) const { - return StringUtils::ciLess(x.mId, y.mId); + return Misc::StringUtils::ciLess(x.mId, y.mId); } template <> inline bool RecordCmp::operator()(const ESM::Cell &x, const ESM::Cell &y) const { - return StringUtils::ciLess(x.mName, y.mName); + return Misc::StringUtils::ciLess(x.mName, y.mName); } template <> inline bool RecordCmp::operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { - return StringUtils::ciLess(x.mCell, y.mCell); + return Misc::StringUtils::ciLess(x.mCell, y.mCell); } } // end namespace diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 5630bfd5c..4be287810 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -6,6 +6,9 @@ #include "customdata.hpp" #include "cellstore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + namespace MWWorld { void RefData::copy (const RefData& refData) @@ -107,6 +110,9 @@ namespace MWWorld void RefData::setCount (int count) { + if(count == 0) + MWBase::Environment::get().getWorld()->removeRefScript(this); + mCount = count; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 6b9abf508..35024b307 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,5 +1,6 @@ #include "scene.hpp" +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" /// FIXME @@ -7,9 +8,11 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "physicssystem.hpp" #include "player.hpp" #include "localscripts.hpp" #include "esmstore.hpp" +#include "class.hpp" #include "cellfunctors.hpp" @@ -24,13 +27,10 @@ namespace { const MWWorld::Class& class_ = MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.mList.begin(), &cell)); - - int numRefs = cellRefList.mList.size(); int current = 0; for (typename T::List::iterator it = cellRefList.mList.begin(); it != cellRefList.mList.end(); it++) { - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Loading cells", 1, current, numRefs); ++current; if (it->mData.getCount() || it->mData.isEnabled()) @@ -52,10 +52,6 @@ namespace } } } - else - { - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Loading cells", 1, 0, 1); - } } } @@ -98,7 +94,7 @@ namespace MWWorld //mPhysics->removeObject("Unnamed_43"); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getMechanicsManager()->dropActors (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } @@ -164,7 +160,7 @@ namespace MWWorld MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); - mechMgr->addActor(player); + mechMgr->add(player); mechMgr->watchActor(player); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); @@ -172,10 +168,18 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { + Nif::NIFFile::CacheLock cachelock; + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + mRendering.preCellChange(mCurrentCell); // remove active - MWBase::Environment::get().getMechanicsManager()->removeActor (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + + std::string loadingExteriorText; + + loadingExteriorText = gmst.find ("sLoadingMessage3")->getString(); CellStoreCollection::iterator active = mActiveCells.begin(); @@ -211,8 +215,6 @@ namespace MWWorld continue; } } - - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Unloading cells", 0, current, numUnload); unloadCell (active++); ++current; } @@ -261,7 +263,9 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Loading cells", 0, current, numLoad); + //Loading Exterior loading text + MWBase::Environment::get().getWindowManager ()->setLoadingProgress (loadingExteriorText, 0, current, numLoad); + loadCell (cell); ++current; } @@ -320,6 +324,13 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + std::string loadingInteriorText; + loadingInteriorText = gmst.find ("sLoadingMessage2")->getString(); + CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); if(!loadcell) @@ -355,8 +366,6 @@ namespace MWWorld active = mActiveCells.begin(); while (active!=mActiveCells.end()) { - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Unloading cells", 0, current, numUnload); - unloadCell (active++); ++current; } @@ -364,7 +373,9 @@ namespace MWWorld // Load cell. std::cout << "cellName: " << cell->mCell->mName << std::endl; - MWBase::Environment::get().getWindowManager ()->setLoadingProgress ("Loading cells", 0, 0, 1); + //Loading Interior loading text + MWBase::Environment::get().getWindowManager ()->setLoadingProgress (loadingInteriorText, 0, 0, 1); + loadCell (cell); mCurrentCell = cell; @@ -439,7 +450,7 @@ namespace MWWorld void Scene::removeObjectFromScene (const Ptr& ptr) { - MWBase::Environment::get().getMechanicsManager()->removeActor (ptr); + MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); mPhysics->removeObject (ptr.getRefData().getHandle()); mRendering.removeObject (ptr); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index ad6ad1559..2333a95ab 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -3,7 +3,7 @@ #include "../mwrender/renderingmanager.hpp" -#include "physicssystem.hpp" +#include "ptr.hpp" #include "globals.hpp" namespace Ogre @@ -34,9 +34,9 @@ namespace MWRender namespace MWWorld { + class PhysicsSystem; class Player; class CellStore; - class Ptr; class Scene { diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp new file mode 100644 index 000000000..005601cd1 --- /dev/null +++ b/apps/openmw/mwworld/store.cpp @@ -0,0 +1,65 @@ +#include "store.hpp" + +namespace MWWorld { + + +void Store::load(ESM::ESMReader &esm, const std::string &id) +{ + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded! So first, proceed as usual. + + // All cells have a name record, even nameless exterior cells. + std::string idLower = Misc::StringUtils::lowerCase(id); + ESM::Cell *cell = new ESM::Cell; + cell->mName = id; + + // The cell itself takes care of some of the hairy details + cell->load(esm, *mEsmStore); + + if(cell->mData.mFlags & ESM::Cell::Interior) + { + // Store interior cell by name, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(idLower)); + if (oldcell) { + // push the new references on the list of references to manage + oldcell->mContextList.push_back(cell->mContextList.at(0)); + // copy list into new cell + cell->mContextList = oldcell->mContextList; + // have new cell replace old cell + *oldcell = *cell; + } else + mInt[idLower] = *cell; + } + else + { + // Store exterior cells by grid position, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(cell->getGridX(), cell->getGridY())); + if (oldcell) { + // push the new references on the list of references to manage + oldcell->mContextList.push_back(cell->mContextList.at(0)); + // copy list into new cell + cell->mContextList = oldcell->mContextList; + // merge lists of leased references, use newer data in case of conflict + for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); it++) { + // remove reference from current leased ref tracker and add it to new cell + ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefnum); + if (itold != oldcell->mMovedRefs.end()) { + ESM::MovedCellRef target0 = *itold; + ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); + ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefnum); + wipecell->mLeasedRefs.erase(it_lease); + *itold = *it; + } + } + cell->mMovedRefs = oldcell->mMovedRefs; + // have new cell replace old cell + *oldcell = *cell; + } else + mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell; + } + delete cell; +} + +} \ No newline at end of file diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 046de8c63..586e407ff 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -17,8 +17,10 @@ namespace MWWorld virtual void setUp() {} virtual void listIdentifier(std::vector &list) const {} - virtual int getSize() const = 0; + virtual size_t getSize() const = 0; virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + + virtual bool eraseStatic(const std::string &id) {return false;} }; template @@ -85,7 +87,7 @@ namespace MWWorld template class Store : public StoreBase { - std::vector mStatic; + std::map mStatic; std::vector mShared; std::map mDynamic; @@ -105,13 +107,12 @@ namespace MWWorld const T *search(const std::string &id) const { T item; - item.mId = StringUtils::lowerCase(id); + item.mId = Misc::StringUtils::lowerCase(id); - typename std::vector::const_iterator it = - std::lower_bound(mStatic.begin(), mStatic.end(), item, RecordCmp()); + typename std::map::const_iterator it = mStatic.find(item.mId); - if (it != mStatic.end() && StringUtils::ciEqual(it->mId, id)) { - return &(*it); + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return &(it->second); } typename Dynamic::const_iterator dit = mDynamic.find(item.mId); @@ -133,18 +134,19 @@ namespace MWWorld } void load(ESM::ESMReader &esm, const std::string &id) { - mStatic.push_back(T()); - mStatic.back().mId = StringUtils::lowerCase(id); - mStatic.back().load(esm); + std::string idLower = Misc::StringUtils::lowerCase(id); + mStatic[idLower] = T(); + mStatic[idLower].mId = idLower; + mStatic[idLower].load(esm); } void setUp() { - std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); + //std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); mShared.reserve(mStatic.size()); - typename std::vector::iterator it = mStatic.begin(); + typename std::map::iterator it = mStatic.begin(); for (; it != mStatic.end(); ++it) { - mShared.push_back(&(*it)); + mShared.push_back(&(it->second)); } } @@ -156,7 +158,7 @@ namespace MWWorld return mShared.end(); } - int getSize() const { + size_t getSize() const { return mShared.size(); } @@ -169,7 +171,7 @@ namespace MWWorld } T *insert(const T &item) { - std::string id = StringUtils::lowerCase(item.mId); + std::string id = Misc::StringUtils::lowerCase(item.mId); std::pair result = mDynamic.insert(std::pair(id, item)); T *ptr = &result.first->second; @@ -181,8 +183,30 @@ namespace MWWorld return ptr; } + bool eraseStatic(const std::string &id) { + T item; + item.mId = Misc::StringUtils::lowerCase(id); + + // delete from the static part of mShared + typename std::vector::iterator sharedIter = mShared.begin(); + for (; sharedIter != (mShared.begin()+mStatic.size()); ++sharedIter) { + if((*sharedIter)->mId == item.mId) { + mShared.erase(sharedIter); + break; + } + } + + typename std::map::iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + mStatic.erase(it); + } + + return true; + } + bool erase(const std::string &id) { - std::string key = StringUtils::lowerCase(id); + std::string key = Misc::StringUtils::lowerCase(id); typename Dynamic::iterator it = mDynamic.find(key); if (it == mDynamic.end()) { return false; @@ -204,39 +228,54 @@ namespace MWWorld template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - mStatic.push_back(ESM::Dialogue()); - mStatic.back().mId = id; - mStatic.back().load(esm); + std::string idLower = Misc::StringUtils::lowerCase(id); + + std::map::iterator it = mStatic.find(idLower); + if (it == mStatic.end()) { + it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; + it->second.mId = id; // don't smash case here, as this line is printed... I think + } + + //I am not sure is it need to load the dialog from a plugin if it was already loaded from prevois plugins + it->second.load(esm); } template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - mStatic.push_back(ESM::Script()); - mStatic.back().load(esm); - StringUtils::toLower(mStatic.back().mId); + ESM::Script scpt; + scpt.load(esm); + Misc::StringUtils::toLower(scpt.mId); + mStatic[scpt.mId] = scpt; } template <> class Store : public StoreBase { - std::vector mStatic; + // For multiple ESM/ESP files we need one list per file. + typedef std::vector LandTextureList; + std::vector mStatic; public: Store() { - mStatic.reserve(128); + mStatic.push_back(LandTextureList()); + LandTextureList <exl = mStatic[0]; + // More than enough to hold Morrowind.esm. Extra lists for plugins will we + // added on-the-fly in a different method. + ltexl.reserve(128); } typedef std::vector::const_iterator iterator; - const ESM::LandTexture *search(size_t index) const { - if (index < mStatic.size()) { - return &mStatic.at(index); - } - return 0; + const ESM::LandTexture *search(size_t index, size_t plugin) const { + assert(plugin < mStatic.size()); + const LandTextureList <exl = mStatic[plugin]; + + assert(index < ltexl.size()); + return <exl.at(index); } - const ESM::LandTexture *find(size_t index) const { - const ESM::LandTexture *ptr = search(index); + const ESM::LandTexture *find(size_t index, size_t plugin) const { + const ESM::LandTexture *ptr = search(index, plugin); if (ptr == 0) { std::ostringstream msg; msg << "Land texture with index " << index << " not found"; @@ -245,27 +284,44 @@ namespace MWWorld return ptr; } - int getSize() const { + size_t getSize() const { return mStatic.size(); } - void load(ESM::ESMReader &esm, const std::string &id) { - ESM::LandTexture ltex; - ltex.load(esm); + size_t getSize(size_t plugin) const { + assert(plugin < mStatic.size()); + return mStatic[plugin].size(); + } - if (ltex.mIndex >= (int) mStatic.size()) { - mStatic.resize(ltex.mIndex + 1); + void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) { + ESM::LandTexture lt; + lt.load(esm); + lt.mId = id; + + // Make sure we have room for the structure + if (plugin >= mStatic.size()) { + mStatic.resize(plugin+1); } - mStatic[ltex.mIndex] = ltex; - mStatic[ltex.mIndex].mId = id; + LandTextureList <exl = mStatic[plugin]; + if(lt.mIndex + 1 > (int)ltexl.size()) + ltexl.resize(lt.mIndex+1); + + // Store it + ltexl[lt.mIndex] = lt; } - iterator begin() const { - return mStatic.begin(); + void load(ESM::ESMReader &esm, const std::string &id) { + load(esm, id, esm.getIndex()); } - iterator end() const { - return mStatic.end(); + iterator begin(size_t plugin) const { + assert(plugin < mStatic.size()); + return mStatic[plugin].begin(); + } + + iterator end(size_t plugin) const { + assert(plugin < mStatic.size()); + return mStatic[plugin].end(); } }; @@ -297,7 +353,7 @@ namespace MWWorld } - int getSize() const { + size_t getSize() const { return mStatic.size(); } @@ -357,19 +413,18 @@ namespace MWWorld } }; - std::vector mInt; - std::vector mExt; + typedef std::map DynamicInt; + typedef std::map, ESM::Cell> DynamicExt; + + DynamicInt mInt; + DynamicExt mExt; std::vector mSharedInt; std::vector mSharedExt; - typedef std::map DynamicInt; - typedef std::map, ESM::Cell> DynamicExt; - DynamicInt mDynamicInt; DynamicExt mDynamicExt; - const ESM::Cell *search(const ESM::Cell &cell) const { if (cell.isExterior()) { return search(cell.getGridX(), cell.getGridY()); @@ -378,6 +433,8 @@ namespace MWWorld } public: + ESMStore *mEsmStore; + typedef SharedIterator iterator; Store() @@ -385,13 +442,12 @@ namespace MWWorld const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; - cell.mName = StringUtils::lowerCase(id); + cell.mName = Misc::StringUtils::lowerCase(id); - std::vector::const_iterator it = - std::lower_bound(mInt.begin(), mInt.end(), cell, RecordCmp()); + std::map::const_iterator it = mInt.find(cell.mName); - if (it != mInt.end() && StringUtils::ciEqual(it->mName, id)) { - return &(*it); + if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + return &(it->second); } DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); @@ -406,14 +462,12 @@ namespace MWWorld ESM::Cell cell; cell.mData.mX = x, cell.mData.mY = y; - std::vector::const_iterator it = - std::lower_bound(mExt.begin(), mExt.end(), cell, ExtCmp()); - - if (it != mExt.end() && it->mData.mX == x && it->mData.mY == y) { - return &(*it); + std::pair key(x, y); + std::map, ESM::Cell>::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); } - std::pair key(x, y); DynamicExt::const_iterator dit = mDynamicExt.find(key); if (dit != mDynamicExt.end()) { return &dit->second; @@ -422,6 +476,30 @@ namespace MWWorld return 0; } + const ESM::Cell *searchOrCreate(int x, int y) { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + std::map, ESM::Cell>::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + ESM::Cell *newCell = new ESM::Cell; + newCell->mData.mX = x; + newCell->mData.mY = y; + mExt[std::make_pair(x, y)] = *newCell; + delete newCell; + + return &mExt[std::make_pair(x, y)]; + } + const ESM::Cell *find(const std::string &id) const { const ESM::Cell *ptr = search(id); if (ptr == 0) { @@ -443,32 +521,28 @@ namespace MWWorld } void setUp() { - typedef std::vector::iterator Iterator; + //typedef std::vector::iterator Iterator; + typedef std::map, ESM::Cell>::iterator ExtIterator; + typedef std::map::iterator IntIterator; - std::sort(mInt.begin(), mInt.end(), RecordCmp()); + //std::sort(mInt.begin(), mInt.end(), RecordCmp()); mSharedInt.reserve(mInt.size()); - for (Iterator it = mInt.begin(); it != mInt.end(); ++it) { - mSharedInt.push_back(&(*it)); + for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { + mSharedInt.push_back(&(it->second)); } - std::sort(mExt.begin(), mExt.end(), ExtCmp()); + //std::sort(mExt.begin(), mExt.end(), ExtCmp()); mSharedExt.reserve(mExt.size()); - for (Iterator it = mExt.begin(); it != mExt.end(); ++it) { - mSharedExt.push_back(&(*it)); + for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { + mSharedExt.push_back(&(it->second)); } } - void load(ESM::ESMReader &esm, const std::string &id) { - ESM::Cell cell; - cell.mName = id; - cell.load(esm); - - if (cell.isExterior()) { - mExt.push_back(cell); - } else { - mInt.push_back(cell); - } - } + // HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get + // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. + // There some nasty three-way cyclic header dependency involved, which I could only fix by moving + // this method. + void load(ESM::ESMReader &esm, const std::string &id); iterator intBegin() const { return iterator(mSharedInt.begin()); @@ -490,7 +564,7 @@ namespace MWWorld const ESM::Cell *searchExtByName(const std::string &id) const { std::vector::const_iterator it = mSharedExt.begin(); for (; it != mSharedExt.end(); ++it) { - if (StringUtils::ciEqual((*it)->mName, id)) { + if (Misc::StringUtils::ciEqual((*it)->mName, id)) { return *it; } } @@ -501,14 +575,14 @@ namespace MWWorld const ESM::Cell *searchExtByRegion(const std::string &id) const { std::vector::const_iterator it = mSharedExt.begin(); for (; it != mSharedExt.end(); ++it) { - if (StringUtils::ciEqual((*it)->mRegion, id)) { + if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { return *it; } } return 0; } - int getSize() const { + size_t getSize() const { return mSharedInt.size() + mSharedExt.size(); } @@ -541,7 +615,7 @@ namespace MWWorld ptr = &result.first->second; mSharedExt.push_back(ptr); } else { - std::string key = StringUtils::lowerCase(cell.mName); + std::string key = Misc::StringUtils::lowerCase(cell.mName); // duplicate insertions are avoided by search(ESM::Cell &) std::pair result = @@ -561,7 +635,7 @@ namespace MWWorld } bool erase(const std::string &id) { - std::string key = StringUtils::lowerCase(id); + std::string key = Misc::StringUtils::lowerCase(id); DynamicInt::iterator it = mDynamicInt.find(key); if (it == mDynamicInt.end()) { @@ -642,7 +716,7 @@ namespace MWWorld mStatic.back().load(esm); } - int getSize() const { + size_t getSize() const { return mStatic.size(); } @@ -691,7 +765,7 @@ namespace MWWorld pg.mCell = name; iterator it = std::lower_bound(mIntBegin, mIntEnd, pg, RecordCmp()); - if (it != mIntEnd && StringUtils::ciEqual(it->mCell, name)) { + if (it != mIntEnd && Misc::StringUtils::ciEqual(it->mCell, name)) { return &(*it); } return 0; @@ -871,7 +945,7 @@ namespace MWWorld } } - int getSize() const { + size_t getSize() const { return mStatic.size(); } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 009b325c0..514276f9b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -497,7 +497,7 @@ void WeatherManager::update(float duration) if (exterior) { std::string regionstr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion; - boost::algorithm::to_lower(regionstr); + Misc::StringUtils::toLower(regionstr); if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) { @@ -718,14 +718,14 @@ void WeatherManager::update(float duration) mRendering->getSkyManager()->setLightningStrength(0.f); mRendering->setAmbientColour(result.mAmbientColor); - mRendering->sunEnable(); + mRendering->sunEnable(false); mRendering->setSunColour(result.mSunColor); mRendering->getSkyManager()->setWeather(result); } else { - mRendering->sunDisable(); + mRendering->sunDisable(false); mRendering->skyDisable(); mRendering->getSkyManager()->setLightningStrength(0.f); } @@ -738,7 +738,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(ambientSnd); - MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } @@ -749,7 +749,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(rainSnd); - MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f0b2efcec..33a9b52bf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,19 +1,28 @@ #include "worldimp.hpp" +#include + #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/scriptmanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" #include "../mwrender/sky.hpp" #include "../mwrender/player.hpp" +#include "../mwclass/door.hpp" + #include "player.hpp" #include "manualref.hpp" #include "cellfunctors.hpp" +#include "containerstore.hpp" using namespace Ogre; @@ -51,7 +60,7 @@ namespace for (iterator iter (refList.mList.begin()); iter!=refList.mList.end(); ++iter) { - if(iter->mData.getCount() > 0 && iter->mData.getBaseNode()){ + if (iter->mData.getCount() > 0 && iter->mData.getBaseNode()){ if (iter->mData.getHandle()==handle) { return &*iter; @@ -167,27 +176,59 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const std::string& encoding, std::map fallbackMap) + const std::vector& master, const std::vector& plugins, + const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), - mNumFacing(0) + mNumFacing(0), mActivationDistanceOverride (mActivationDistanceOverride), + mFallback (fallbackMap) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); mRendering = new MWRender::RenderingManager(renderer, resDir, cacheDir, mPhysEngine); + mPhysEngine->setSceneManager(renderer.getScene()); + mWeatherManager = new MWWorld::WeatherManager(mRendering); - boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master)); + int idx = 0; + // NOTE: We might need to reserve one more for the running game / save. + mEsm.resize(master.size() + plugins.size()); + for (std::vector::size_type i = 0; i < master.size(); i++, idx++) + { + boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); - std::cout << "Loading ESM " << masterPath.string() << "\n"; + std::cout << "Loading ESM " << masterPath.string() << "\n"; - // This parses the ESM file and loads a sample cell - mEsm.setEncoding(encoding); - mEsm.open (masterPath.string()); - mStore.load (mEsm); + // This parses the ESM file + ESM::ESMReader lEsm; + lEsm.setEncoder(encoder); + lEsm.setIndex(idx); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open (masterPath.string()); + mEsm[idx] = lEsm; + mStore.load (mEsm[idx]); + } + + for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) + { + boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); + + std::cout << "Loading ESP " << pluginPath.string() << "\n"; + + // This parses the ESP file + ESM::ESMReader lEsm; + lEsm.setEncoder(encoder); + lEsm.setIndex(idx); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open (pluginPath.string()); + mEsm[idx] = lEsm; + mStore.load (mEsm[idx]); + } + + mStore.setUp(); mPlayer = new MWWorld::Player (mStore.get().find ("player"), *this); mRendering->attachCameraTo(mPlayer->getPlayer()); @@ -207,8 +248,6 @@ namespace MWWorld mWorldScene = new Scene(*mRendering, mPhysics); - setFallbackValues(fallbackMap); - lastTick = mTimer.getMilliseconds(); } @@ -237,7 +276,7 @@ namespace MWWorld MWWorld::Store::iterator it = regions.begin(); for (; it != regions.end(); ++it) { - if (MWWorld::StringUtils::ciEqual(cellName, it->mName)) + if (Misc::StringUtils::ciEqual(cellName, it->mName)) { return mStore.get().searchExtByRegion(it->mId); } @@ -266,7 +305,7 @@ namespace MWWorld return mStore; } - ESM::ESMReader& World::getEsmReader() + std::vector& World::getEsmReader() { return mEsm; } @@ -296,6 +335,52 @@ namespace MWWorld return mGlobalVariables->getType (name); } + std::vector World::getGlobals () const + { + return mGlobalVariables->getGlobals(); + } + + std::string World::getCurrentCellName () const + { + std::string name; + + Ptr::CellStore *cell = mWorldScene->getCurrentCell(); + if (cell->mCell->isExterior()) + { + if (cell->mCell->mName != "") + { + name = cell->mCell->mName; + } + else + { + const ESM::Region* region = + MWBase::Environment::get().getWorld()->getStore().get().search(cell->mCell->mRegion); + if (region) + name = region->mName; + else + { + const ESM::GameSetting *setting = + MWBase::Environment::get().getWorld()->getStore().get().search("sDefaultCellname"); + + if (setting && setting->mValue.getType()==ESM::VT_String) + name = setting->mValue.getString(); + } + + } + } + else + { + name = cell->mCell->mName; + } + + return name; + } + + void World::removeRefScript (MWWorld::RefData *ref) + { + mLocalScripts.remove (ref); + } + Ptr World::getPtr (const std::string& name, bool activeOnly) { // the player is always in an active cell. @@ -351,6 +436,26 @@ namespace MWWorld return MWWorld::Ptr(); } + void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell) + { + if( reference.getTypeName()==typeid (ESM::Container).name() || + reference.getTypeName()==typeid (ESM::NPC).name() || + reference.getTypeName()==typeid (ESM::Creature).name()) + { + MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference); + for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) + { + std::string script = MWWorld::Class::get(*it).getScript(*it); + if(script != "") + { + MWWorld::Ptr item = *it; + item.mCell = cell; + mLocalScripts.add (script, item); + } + } + } + } + void World::enable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) @@ -362,6 +467,25 @@ namespace MWWorld } } + void World::removeContainerScripts(const Ptr& reference) + { + if( reference.getTypeName()==typeid (ESM::Container).name() || + reference.getTypeName()==typeid (ESM::NPC).name() || + reference.getTypeName()==typeid (ESM::Creature).name()) + { + MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference); + for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) + { + std::string script = MWWorld::Class::get(*it).getScript(*it); + if(script != "") + { + MWWorld::Ptr item = *it; + mLocalScripts.remove (item); + } + } + } + } + void World::disable (const Ptr& reference) { if (reference.getRefData().isEnabled()) @@ -528,23 +652,55 @@ namespace MWWorld return mWorldScene->markCellAsUnchanged(); } - std::string World::getFacedHandle() + float World::getMaxActivationDistance () { + if (mActivationDistanceOverride >= 0) + return mActivationDistanceOverride; + + return (std::max) (getNpcActivationDistance (), getObjectActivationDistance ()); + } + + float World::getNpcActivationDistance () + { + if (mActivationDistanceOverride >= 0) + return mActivationDistanceOverride; + + return getStore().get().find ("iMaxActivateDist")->getInt()*5/4; + } + + float World::getObjectActivationDistance () + { + if (mActivationDistanceOverride >= 0) + return mActivationDistanceOverride; + + return getStore().get().find ("iMaxActivateDist")->getInt(); + } + + MWWorld::Ptr World::getFacedObject() + { + std::pair result; + if (!mRendering->occlusionQuerySupported()) - { - std::pair result = mPhysics->getFacedHandle (*this); - - if (result.first.empty() || - result.second>getStore().get().find ("iMaxActivateDist")->getInt()) - return ""; - - return result.first; - } + result = mPhysics->getFacedHandle (*this, getMaxActivationDistance ()); else - { - // updated every few frames in update() - return mFacedHandle; - } + result = std::make_pair (mFacedDistance, mFacedHandle); + + if (result.second.empty()) + return MWWorld::Ptr (); + + MWWorld::Ptr object = searchPtrViaHandle (result.second); + + float ActivationDistance; + + if (object.getTypeName ().find("NPC") != std::string::npos) + ActivationDistance = getNpcActivationDistance (); + else + ActivationDistance = getObjectActivationDistance (); + + if (result.first > ActivationDistance) + return MWWorld::Ptr (); + + return object; } void World::deleteObject (const Ptr& ptr) @@ -558,24 +714,17 @@ namespace MWWorld { mWorldScene->removeObjectFromScene (ptr); mLocalScripts.remove (ptr); + removeContainerScripts (ptr); } } } - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z) { ESM::Position &pos = ptr.getRefData().getPosition(); - pos.pos[0] = x, pos.pos[1] = y, pos.pos[2] = z; + pos.pos[0] = x; + pos.pos[1] = y; + pos.pos[2] = z; Ogre::Vector3 vec(x, y, z); CellStore *currCell = ptr.getCell(); @@ -584,23 +733,33 @@ namespace MWWorld if (*currCell != newCell) { + removeContainerScripts(ptr); + if (isPlayer) + { if (!newCell.isExterior()) - changeToInteriorCell(toLower(newCell.mCell->mName), pos); + changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.mCell->mName), pos); else { int cellX = newCell.mCell->getGridX(); int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } - else { + } + else + { if (!mWorldScene->isCellActive(*currCell)) copyObjectToCell(ptr, newCell, pos); else if (!mWorldScene->isCellActive(newCell)) { - MWWorld::Class::get(ptr).copyToCell(ptr, newCell); + MWWorld::Class::get(ptr) + .copyToCell(ptr, newCell) + .getRefData() + .setBaseNode(0); + mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); + removeContainerScripts (ptr); haveToMove = false; } else @@ -608,25 +767,19 @@ namespace MWWorld MWWorld::Ptr copy = MWWorld::Class::get(ptr).copyToCell(ptr, newCell); - mRendering->moveObjectToCell(copy, vec, currCell); + mRendering->updateObjectCell(ptr, copy); - if (MWWorld::Class::get(ptr).isActor()) - { - MWBase::MechanicsManager *mechMgr = - MWBase::Environment::get().getMechanicsManager(); + MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); + mechMgr->updateCell(ptr, copy); - mechMgr->removeActor(ptr); - mechMgr->addActor(copy); - } - else + std::string script = + MWWorld::Class::get(ptr).getScript(ptr); + if (!script.empty()) { - std::string script = - MWWorld::Class::get(ptr).getScript(ptr); - if (!script.empty()) - { - mLocalScripts.remove(ptr); - mLocalScripts.add(script, copy); - } + mLocalScripts.remove(ptr); + removeContainerScripts (ptr); + mLocalScripts.add(script, copy); + addContainerScripts (copy, &newCell); } } ptr.getRefData().setCount(0); @@ -676,16 +829,16 @@ namespace MWWorld rot.y = Ogre::Degree(y).valueRadians(); rot.z = Ogre::Degree(z).valueRadians(); - float *objRot = ptr.getRefData().getPosition().rot; - if(ptr.getRefData().getBaseNode() == 0 || !mRendering->rotateObject(ptr, rot, adjust)) + if (mRendering->rotateObject(ptr, rot, adjust)) { - objRot[0] = (adjust ? objRot[0] + rot.x : rot.x), objRot[1] = (adjust ? objRot[1] + rot.y : rot.y), objRot[2] = (adjust ? objRot[2] + rot.z : rot.z); - return; - } + // rotate physically iff renderer confirm so + float *objRot = ptr.getRefData().getPosition().rot; + objRot[0] = rot.x, objRot[1] = rot.y, objRot[2] = rot.z; - // do this after rendering rotated the object so it gets changed by Class->adjustRotation - objRot[0] = rot.x, objRot[1] = rot.y, objRot[2] = rot.z; - mPhysics->rotateObject(ptr); + if (ptr.getRefData().getBaseNode() != 0) { + mPhysics->rotateObject(ptr); + } + } } void World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) @@ -722,53 +875,33 @@ namespace MWWorld --cellY; } - void World::doPhysics (const std::vector >& actors, - float duration) + void World::doPhysics(const PtrMovementList &actors, float duration) { - mPhysics->doPhysics(duration, actors); + /* No duration? Shouldn't be any movement, then. */ + if(duration <= 0.0f) + return; - const int tick = 16; // 16 ms ^= 60 Hz - - // Game clock part of the loop, contains everything that has to be executed in a fixed timestep - long long dt = mTimer.getMilliseconds() - lastTick; - if (dt >= 100) + PtrMovementList::const_iterator player(actors.end()); + for(PtrMovementList::const_iterator iter(actors.begin());iter != actors.end();iter++) { - // throw away wall clock time if necessary to keep the framerate above the minimum of 10 fps - lastTick += (dt - 100); - dt = 100; + if(iter->first.getRefData().getHandle() == "player") + { + /* Handle player last, in case a cell transition occurs */ + player = iter; + continue; + } + Ogre::Vector3 vec = mPhysics->move(iter->first, iter->second, duration, + !isSwimming(iter->first) && !isFlying(iter->first)); + moveObjectImp(iter->first, vec.x, vec.y, vec.z); } - while (dt >= tick) + if(player != actors.end()) { - dt -= tick; - lastTick += tick; - - std::vector< std::pair > vectors = mPhysics->doPhysicsFixed (actors); - - std::vector< std::pair >::iterator player = vectors.end(); - - for (std::vector< std::pair >::iterator it = vectors.begin(); - it!= vectors.end(); ++it) - { - if (it->first=="player") - { - player = it; - } - else - { - MWWorld::Ptr ptr = getPtrViaHandle (it->first); - moveObjectImp (ptr, it->second.x, it->second.y, it->second.z); - } - } - - // Make sure player is moved last (otherwise the cell might change in the middle of an update - // loop) - if (player!=vectors.end()) - { - if (moveObjectImp (getPtrViaHandle (player->first), - player->second.x, player->second.y, player->second.z) == true) - return; // abort the current loop if the cell has changed - } + Ogre::Vector3 vec = mPhysics->move(player->first, player->second, duration, + !isSwimming(player->first) && !isFlying(player->first)); + moveObjectImp(player->first, vec.x, vec.y, vec.z); } + // the only purpose this has currently is to update the debug drawer + mPhysEngine->stepSimulation (duration); } bool World::toggleCollisionMode() @@ -805,7 +938,7 @@ namespace MWWorld { bool update = false; - if (StringUtils::ciEqual(record.mId, "player")) + if (Misc::StringUtils::ciEqual(record.mId, "player")) { static const char *sRaces[] = { @@ -816,7 +949,7 @@ namespace MWWorld int i=0; for (; sRaces[i]; ++i) - if (StringUtils::ciEqual (sRaces[i], record.mRace)) + if (Misc::StringUtils::ciEqual (sRaces[i], record.mRace)) break; mGlobalVariables->setInt ("pcrace", sRaces[i] ? i+1 : 0); @@ -825,9 +958,9 @@ namespace MWWorld mPlayer->getPlayer().get()->mBase; update = record.isMale() != player->isMale() || - !StringUtils::ciEqual(record.mRace, player->mRace) || - !StringUtils::ciEqual(record.mHead, player->mHead) || - !StringUtils::ciEqual(record.mHair, player->mHair); + !Misc::StringUtils::ciEqual(record.mRace, player->mRace) || + !Misc::StringUtils::ciEqual(record.mHead, player->mHead) || + !Misc::StringUtils::ciEqual(record.mHair, player->mHair); } const ESM::NPC *ret = mStore.insert(record); if (update) { @@ -836,20 +969,9 @@ namespace MWWorld return ret; } - void World::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, - int number) - { - mRendering->playAnimationGroup (ptr, groupName, mode, number); - } - - void World::skipAnimation (const MWWorld::Ptr& ptr) - { - mRendering->skipAnimation (ptr); - } - void World::update (float duration, bool paused) { - /// \todo split this function up into subfunctions + mWeatherManager->update (duration); mWorldScene->update (duration, paused); @@ -858,10 +980,15 @@ namespace MWWorld mRendering->getPlayerData(eyepos, pitch, yaw); mPhysics->updatePlayerData(eyepos, pitch, yaw); - mWeatherManager->update (duration); + performUpdateSceneQueries (); + updateWindowManager (); + } + + void World::updateWindowManager () + { // inform the GUI about focused object - MWWorld::Ptr object = searchPtrViaHandle(mFacedHandle); + MWWorld::Ptr object = getFacedObject (); MWBase::Environment::get().getWindowManager()->setFocusObject(object); @@ -869,13 +996,7 @@ namespace MWWorld if (!object.isEmpty ()) { Ogre::SceneNode* node = object.getRefData().getBaseNode(); - Ogre::AxisAlignedBox bounds; - int i; - for (i=0; inumAttachedObjects(); ++i) - { - Ogre::MovableObject* ob = node->getAttachedObject(i); - bounds.merge(ob->getWorldBoundingBox()); - } + Ogre::AxisAlignedBox bounds = node->_getWorldAABB(); if (bounds.isFinite()) { Vector4 screenCoords = mRendering->boundingBoxToScreen(bounds); @@ -883,7 +1004,10 @@ namespace MWWorld screenCoords[0], screenCoords[1], screenCoords[2], screenCoords[3]); } } + } + void World::performUpdateSceneQueries () + { if (!mRendering->occlusionQuerySupported()) { // cast a ray from player to sun to detect if the sun is visible @@ -891,7 +1015,6 @@ namespace MWWorld // currently its here because we need to access the physics system float* p = mPlayer->getPlayer().getRefData().getPosition().pos; Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - sun = Vector3(sun.x, -sun.z, sun.y); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } @@ -902,121 +1025,154 @@ namespace MWWorld MWRender::OcclusionQuery* query = mRendering->getOcclusionQuery(); if (!query->occlusionTestPending()) { - // get result of last query - if (mNumFacing == 0) mFacedHandle = ""; - else if (mNumFacing == 1) - { - bool result = query->getTestResult(); - mFacedHandle = result ? mFaced1Name : ""; - } - else if (mNumFacing == 2) - { - bool result = query->getTestResult(); - mFacedHandle = result ? mFaced2Name : mFaced1Name; - } - - // send new query - // figure out which object we want to test against - std::vector < std::pair < float, std::string > > results; - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - float x, y; - MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedObjects(x, y); - } - else - results = mPhysics->getFacedObjects(); - - // ignore the player and other things we're not interested in - std::vector < std::pair < float, std::string > >::iterator it = results.begin(); - while (it != results.end()) - { - if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain - || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) - { - it = results.erase(it); - } - else - ++it; - } - - if (results.size() == 0) - { - mNumFacing = 0; - } - else if (results.size() == 1) - { - mFaced1 = getPtrViaHandle(results.front().second); - mFaced1Name = results.front().second; - mNumFacing = 1; - - btVector3 p; - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - float x, y; - MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - p = mPhysics->getRayPoint(results.front().first, x, y); - } - else - p = mPhysics->getRayPoint(results.front().first); - Ogre::Vector3 pos(p.x(), p.z(), -p.y()); - Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); - - //std::cout << "Num facing 1 : " << mFaced1Name << std::endl; - //std::cout << "Type 1 " << mFaced1.getTypeName() << std::endl; - - query->occlusionTest(pos, node); - } - else - { - mFaced1Name = results.front().second; - mFaced2Name = results[1].second; - mFaced1 = getPtrViaHandle(results.front().second); - mFaced2 = getPtrViaHandle(results[1].second); - mNumFacing = 2; - - btVector3 p; - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - float x, y; - MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - p = mPhysics->getRayPoint(results[1].first, x, y); - } - else - p = mPhysics->getRayPoint(results[1].first); - Ogre::Vector3 pos(p.x(), p.z(), -p.y()); - Ogre::SceneNode* node1 = mFaced1.getRefData().getBaseNode(); - Ogre::SceneNode* node2 = mFaced2.getRefData().getBaseNode(); - - // no need to test if the first node is not occluder - if (!query->isPotentialOccluder(node1) && (mFaced1.getTypeName().find("Static") == std::string::npos)) - { - mFacedHandle = mFaced1Name; - //std::cout << "node1 Not an occluder" << std::endl; - return; - } - - // no need to test if the second object is static (thus cannot be activated) - if (mFaced2.getTypeName().find("Static") != std::string::npos) - { - mFacedHandle = mFaced1Name; - return; - } - - // work around door problems - if (mFaced1.getTypeName().find("Static") != std::string::npos - && mFaced2.getTypeName().find("Door") != std::string::npos) - { - mFacedHandle = mFaced2Name; - return; - } - - query->occlusionTest(pos, node2); - } + processFacedQueryResults (query); + beginFacedQueryProcess (query); } } } + void World::processFacedQueryResults (MWRender::OcclusionQuery* query) + { + // get result of last query + if (mNumFacing == 0) + { + mFacedHandle = ""; + mFacedDistance = FLT_MAX; + } + else if (mNumFacing == 1) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced1Name : ""; + mFacedDistance = result ? mFaced1Distance : FLT_MAX; + } + else if (mNumFacing == 2) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced2Name : mFaced1Name; + mFacedDistance = result ? mFaced1Distance : mFaced1Distance; + } + } + + void World::beginFacedQueryProcess (MWRender::OcclusionQuery* query) + { + // send new query + // figure out which object we want to test against + std::vector < std::pair < float, std::string > > results; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()); + } + else + { + results = mPhysics->getFacedHandles(getMaxActivationDistance ()); + } + + // ignore the player and other things we're not interested in + std::vector < std::pair < float, std::string > >::iterator it = results.begin(); + while (it != results.end()) + { + if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain + || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) + { + it = results.erase(it); + } + else + ++it; + } + + if (results.size() == 0) + { + mNumFacing = 0; + } + else if (results.size() == 1) + { + beginSingleFacedQueryProcess (query, results); + } + else + { + beginDoubleFacedQueryProcess (query, results); + } + } + + void World::beginSingleFacedQueryProcess (MWRender::OcclusionQuery* query, std::vector < std::pair < float, std::string > > const & results) + { + mFaced1 = getPtrViaHandle(results.front().second); + mFaced1Name = results.front().second; + mFaced1Distance = results.front().first; + mNumFacing = 1; + + btVector3 p; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + p = mPhysics->getRayPoint(results.front().first, x, y); + } + else + p = mPhysics->getRayPoint(results.front().first); + Ogre::Vector3 pos(p.x(), p.y(), p.z()); + Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); + + //std::cout << "Num facing 1 : " << mFaced1Name << std::endl; + //std::cout << "Type 1 " << mFaced1.getTypeName() << std::endl; + + query->occlusionTest(pos, node); + } + + void World::beginDoubleFacedQueryProcess (MWRender::OcclusionQuery* query, std::vector < std::pair < float, std::string > > const & results) + { + mFaced1Name = results.at (0).second; + mFaced2Name = results.at (1).second; + mFaced1Distance = results.at (0).first; + mFaced2Distance = results.at (1).first; + mFaced1 = getPtrViaHandle(results.at (0).second); + mFaced2 = getPtrViaHandle(results.at (1).second); + mNumFacing = 2; + + btVector3 p; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + p = mPhysics->getRayPoint(results.at (1).first, x, y); + } + else + p = mPhysics->getRayPoint(results.at (1).first); + Ogre::Vector3 pos(p.x(), p.y(), p.z()); + Ogre::SceneNode* node1 = mFaced1.getRefData().getBaseNode(); + Ogre::SceneNode* node2 = mFaced2.getRefData().getBaseNode(); + + // no need to test if the first node is not occluder + if (!query->isPotentialOccluder(node1) && (mFaced1.getTypeName().find("Static") == std::string::npos)) + { + mFacedHandle = mFaced1Name; + mFacedDistance = mFaced1Distance; + //std::cout << "node1 Not an occluder" << std::endl; + return; + } + + // no need to test if the second object is static (thus cannot be activated) + if (mFaced2.getTypeName().find("Static") != std::string::npos) + { + mFacedHandle = mFaced1Name; + mFacedDistance = mFaced1Distance; + return; + } + + // work around door problems + if (mFaced1.getTypeName().find("Static") != std::string::npos + && mFaced2.getTypeName().find("Door") != std::string::npos) + { + mFacedHandle = mFaced2Name; + mFacedDistance = mFaced2Distance; + return; + } + + query->occlusionTest(pos, node2); + } + bool World::isCellExterior() const { Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); @@ -1062,8 +1218,8 @@ namespace MWWorld if (!ref) return Vector2(0, 1); Ogre::SceneNode* node = ref->mData.getBaseNode(); - Vector3 dir = node->_getDerivedOrientation().yAxis(); - Vector2 d = Vector2(dir.x, dir.z); + Vector3 dir = node->_getDerivedOrientation() * Ogre::Vector3(0,1,0); + Vector2 d = Vector2(dir.x, dir.y); return d; } @@ -1072,36 +1228,15 @@ namespace MWWorld std::vector result; MWWorld::CellRefList& doors = cell->mDoors; - std::list< MWWorld::LiveCellRef >& refList = doors.mList; - for (std::list< MWWorld::LiveCellRef >::iterator it = refList.begin(); it != refList.end(); ++it) + CellRefList::List& refList = doors.mList; + for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) { MWWorld::LiveCellRef& ref = *it; if (ref.mRef.mTeleport) { World::DoorMarker newMarker; - - std::string dest; - if (ref.mRef.mDestCell != "") - { - // door leads to an interior, use interior name - dest = ref.mRef.mDestCell; - } - else - { - // door leads to exterior, use cell name (if any), otherwise translated region name - int x,y; - positionToIndex (ref.mRef.mDoorDest.pos[0], ref.mRef.mDoorDest.pos[1], x, y); - const ESM::Cell* cell = mStore.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - dest = mStore.get().find(cell->mRegion)->mName; - } - } - - newMarker.name = dest; + newMarker.name = MWClass::Door::getDestination(ref); ESM::Position pos = ref.mData.getPosition (); @@ -1134,6 +1269,15 @@ namespace MWWorld mRendering->toggleWater(); } + void World::PCDropped (const Ptr& item) + { + std::string script = MWWorld::Class::get(item).getScript(item); + + // Set OnPCDrop Variable on item's script, if it has a script with that variable declared + if(script != "") + item.mRefData->getLocals().setVarByInt(script, "onpcdrop", 1); + } + bool World::placeObject (const Ptr& object, float cursorX, float cursorY) { std::pair result = mPhysics->castRay(cursorX, cursorY); @@ -1145,7 +1289,7 @@ namespace MWWorld if (isCellExterior()) { int cellX, cellY; - positionToIndex(result.second[0], -result.second[2], cellX, cellY); + positionToIndex(result.second[0], result.second[1], cellX, cellY); cell = mCells.getExterior(cellX, cellY); } else @@ -1153,10 +1297,11 @@ namespace MWWorld ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition(); pos.pos[0] = result.second[0]; - pos.pos[1] = -result.second[2]; - pos.pos[2] = result.second[1]; + pos.pos[1] = result.second[1]; + pos.pos[2] = result.second[2]; - copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, *cell, pos); + PCDropped(dropped); object.getRefData().setCount(0); return true; @@ -1173,8 +1318,8 @@ namespace MWWorld return true; } - void - World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) + + Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) { /// \todo add searching correct cell for position specified MWWorld::Ptr dropped = @@ -1196,15 +1341,18 @@ namespace MWWorld if (!script.empty()) { mLocalScripts.add(script, dropped); } + addContainerScripts(dropped, &cell); } + + return dropped; } - void World::dropObjectOnGround (const Ptr& object) + void World::dropObjectOnGround (const Ptr& actor, const Ptr& object) { - MWWorld::Ptr::CellStore* cell = getPlayer().getPlayer().getCell(); + MWWorld::Ptr::CellStore* cell = actor.getCell(); ESM::Position pos = - getPlayer().getPlayer().getRefData().getPosition(); + actor.getRefData().getPosition(); Ogre::Vector3 orig = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); @@ -1217,7 +1365,9 @@ namespace MWWorld mPhysics->castRay(orig, dir, len); pos.pos[2] = hit.second.z; - copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, *cell, pos); + if(actor == mPlayer->getPlayer()) // Only call if dropped by player + PCDropped(dropped); object.getRefData().setCount(0); } @@ -1232,25 +1382,42 @@ namespace MWWorld } bool - World::isSwimming(const MWWorld::Ptr &object) + World::isFlying(const MWWorld::Ptr &ptr) const + { + const MWWorld::Class &cls = MWWorld::Class::get(ptr); + if(cls.isActor() && cls.getCreatureStats(ptr).getMagicEffects().get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0) + return true; + return false; + } + + bool + World::isSwimming(const MWWorld::Ptr &object) const { /// \todo add check ifActor() - only actors can swim float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); - /// \fixme should rely on object height - pos.z += 30; + /// \fixme 3/4ths submerged? + const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); + if(actor) pos.z += actor->getHalfExtents().z * 1.5; - return isUnderwater(*object.getCell()->mCell, pos); + return isUnderwater(object.getCell(), pos); } bool - World::isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos) + World::isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const { - if (!(cell.mData.mFlags & ESM::Cell::HasWater)) { + if (!(cell->mCell->mData.mFlags & ESM::Cell::HasWater)) { return false; } - return pos.z < cell.mWater; + return pos.z < cell->mWaterLevel; + } + + bool World::isOnGround(const MWWorld::Ptr &ptr) const + { + RefData &refdata = ptr.getRefData(); + const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + return physactor && physactor->getOnGround(); } void World::renderPlayer() @@ -1267,23 +1434,35 @@ namespace MWWorld { Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); - Ogre::Vector3 playerPos; - float* pos = mPlayer->getPlayer ().getRefData ().getPosition ().pos; - playerPos.x = pos[0]; - playerPos.y = pos[1]; - playerPos.z = pos[2]; + RefData &refdata = mPlayer->getPlayer().getRefData(); + Ogre::Vector3 playerPos(refdata.getPosition().pos); - std::pair hit = - mPhysics->castRay(playerPos, Ogre::Vector3(0,0,-1), 50); - bool isOnGround = (hit.first ? (hit.second.distance (playerPos) < 25) : false); - - if (!isOnGround || isUnderwater (*currentCell->mCell, playerPos)) + const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + if(!physactor->getOnGround() || isUnderwater(currentCell, playerPos)) return 2; - - if (currentCell->mCell->mData.mFlags & ESM::Cell::NoSleep) + if((currentCell->mCell->mData.mFlags&ESM::Cell::NoSleep)) return 1; return 0; + } + MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr) + { + return mRendering->getAnimation(ptr); + } + + void World::playVideo (const std::string &name, bool allowSkipping) + { + mRendering->playVideo(name, allowSkipping); + } + + void World::stopVideo () + { + mRendering->stopVideo(); + } + + void World::frameStarted (float dt) + { + mRendering->frameStarted(dt); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0962c292c..fe4ceff69 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -37,6 +37,7 @@ namespace MWRender { class SkyManager; class CellRender; + class Animation; } namespace MWWorld @@ -54,7 +55,7 @@ namespace MWWorld MWWorld::Scene *mWorldScene; MWWorld::Player *mPlayer; - ESM::ESMReader mEsm; + std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals *mGlobalVariables; @@ -71,11 +72,15 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + int mActivationDistanceOverride; std::string mFacedHandle; + float mFacedDistance; Ptr mFaced1; Ptr mFaced2; std::string mFaced1Name; std::string mFaced2Name; + float mFaced1Distance; + float mFaced2Distance; int mNumFacing; std::map mFallback; @@ -87,15 +92,31 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - virtual void - copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + + Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + + void updateWindowManager (); + void performUpdateSceneQueries (); + void processFacedQueryResults (MWRender::OcclusionQuery* query); + void beginFacedQueryProcess (MWRender::OcclusionQuery* query); + void beginSingleFacedQueryProcess (MWRender::OcclusionQuery* query, std::vector < std::pair < float, std::string > > const & results); + void beginDoubleFacedQueryProcess (MWRender::OcclusionQuery* query, std::vector < std::pair < float, std::string > > const & results); + + float getMaxActivationDistance (); + float getNpcActivationDistance (); + float getObjectActivationDistance (); + + void removeContainerScripts(const Ptr& reference); + void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell); + void PCDropped (const Ptr& item); public: World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const std::string& encoding, std::map fallbackMap); + const std::vector& master, const std::vector& plugins, + const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); virtual ~World(); @@ -124,7 +145,7 @@ namespace MWWorld virtual const MWWorld::ESMStore& getStore() const; - virtual ESM::ESMReader& getEsmReader(); + virtual std::vector& getEsmReader(); virtual LocalScripts& getLocalScripts(); @@ -154,6 +175,13 @@ namespace MWWorld virtual char getGlobalVariableType (const std::string& name) const; ///< Return ' ', if there is no global variable with this name. + virtual std::vector getGlobals () const; + + virtual std::string getCurrentCellName () const; + + virtual void removeRefScript (MWWorld::RefData *ref); + //< Remove the script attached to ref from mLocalScripts + virtual Ptr getPtr (const std::string& name, bool activeOnly); ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. @@ -213,8 +241,8 @@ namespace MWWorld virtual void markCellAsUnchanged(); - virtual std::string getFacedHandle(); - ///< Return handle of the object the player is looking at + virtual MWWorld::Ptr getFacedObject(); + ///< Return pointer to the object the player is looking at, if it is within activation range virtual void deleteObject (const Ptr& ptr); @@ -228,7 +256,7 @@ namespace MWWorld virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); - ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; @@ -237,8 +265,7 @@ namespace MWWorld virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const; ///< Convert position to cell numbers - virtual void doPhysics (const std::vector >& actors, - float duration); + virtual void doPhysics(const PtrMovementList &actors, float duration); ///< Run physics simulation and modify \a world accordingly. virtual bool toggleCollisionMode(); @@ -271,18 +298,6 @@ namespace MWWorld /// \return pointer to created record - virtual void playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, - int mode, int number = 1); - ///< Run animation for a MW-reference. Calls to this function for references that are - /// currently not in the rendered scene should be ignored. - /// - /// \param mode: 0 normal, 1 immediate start, 2 immediate loop - /// \param number How offen the animation should be run - - virtual void skipAnimation (const MWWorld::Ptr& ptr); - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the rendered scene should be ignored. - virtual void update (float duration, bool paused); virtual bool placeObject (const Ptr& object, float cursorX, float cursorY); @@ -292,15 +307,17 @@ namespace MWWorld /// @param cursor Y (relative 0-1) /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const Ptr& object); + virtual void dropObjectOnGround (const Ptr& actor, const Ptr& object); virtual bool canPlaceObject(float cursorX, float cursorY); ///< @return true if it is possible to place on object at specified cursor location virtual void processChangedSettings(const Settings::CategorySettingVector& settings); - virtual bool isSwimming(const MWWorld::Ptr &object); - virtual bool isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos); + virtual bool isFlying(const MWWorld::Ptr &ptr) const; + virtual bool isSwimming(const MWWorld::Ptr &object) const; + virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const; + virtual bool isOnGround(const MWWorld::Ptr &ptr) const; virtual void togglePOV() { mRendering->togglePOV(); @@ -322,8 +339,12 @@ namespace MWWorld mRendering->togglePlayerLooking(enable); } + virtual void changeVanityModeScale(float factor) { + mRendering->changeVanityModeScale(factor); + } + virtual void renderPlayer(); - + virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); virtual int canRest(); @@ -332,6 +353,14 @@ namespace MWWorld /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + /// \todo Probably shouldn't be here + virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping); + virtual void stopVideo(); + virtual void frameStarted (float dt); }; } diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 8d5ea2f1e..97feddffe 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -27,6 +27,8 @@ macro(_FIND_BULLET_LIBRARY _var) ${ARGN} PATHS ${BULLET_ROOT} + ${BULLET_ROOT}/lib/Debug + ${BULLET_ROOT}/lib/Release ${BULLET_ROOT}/out/release8/libs ${BULLET_ROOT}/out/debug8/libs PATH_SUFFIXES lib diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake deleted file mode 100644 index 2e755d047..000000000 --- a/cmake/FindFFMPEG.cmake +++ /dev/null @@ -1,105 +0,0 @@ -# Find the FFmpeg library -# -# Sets -# FFMPEG_FOUND. If false, don't try to use ffmpeg -# FFMPEG_INCLUDE_DIR -# FFMPEG_LIBRARIES -# -# Modified by Nicolay Korslund for OpenMW - -SET( FFMPEG_FOUND "NO" ) - -FIND_PATH( FFMPEG_general_INCLUDE_DIR libavcodec/avcodec.h libavformat/avformat.h - HINTS - PATHS - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec - ) - -FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavcodec - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec -) - -FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavformat - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavformat - /usr/local/include/ffmpeg/libavformat - /usr/include/libavformat - /usr/local/include/libavformat -) - -set(FFMPEG_INCLUDE_DIR ${FFMPEG_general_INCLUDE_DIR} ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) - -IF( FFMPEG_INCLUDE_DIR ) - -FIND_PROGRAM( FFMPEG_CONFIG ffmpeg-config - /usr/bin - /usr/local/bin - ${HOME}/bin -) - -IF( FFMPEG_CONFIG ) - EXEC_PROGRAM( ${FFMPEG_CONFIG} ARGS "--libs avformat" OUTPUT_VARIABLE FFMPEG_LIBS ) - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES "${FFMPEG_LIBS}" ) - -ELSE( FFMPEG_CONFIG ) - - FIND_LIBRARY( FFMPEG_avcodec_LIBRARY avcodec - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avformat_LIBRARY avformat - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avutil_LIBRARY avutil - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - IF( FFMPEG_avcodec_LIBRARY ) - IF( FFMPEG_avformat_LIBRARY ) - - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES ${FFMPEG_avformat_LIBRARY} ${FFMPEG_avcodec_LIBRARY} ) - IF( FFMPEG_avutil_LIBRARY ) - SET( FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_avutil_LIBRARY} ) - ENDIF( FFMPEG_avutil_LIBRARY ) - - ENDIF( FFMPEG_avformat_LIBRARY ) - ENDIF( FFMPEG_avcodec_LIBRARY ) - -ENDIF( FFMPEG_CONFIG ) - -ENDIF( FFMPEG_INCLUDE_DIR ) diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 000000000..4147590d6 --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1,156 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionaly set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVUTIL +# - POSTPROCESS +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${FFMPEGSDK_INC} + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${FFMPEGSDK_LIB} + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + set (FFMPEGSDK ENV${FFMPEG_HOME}) + if (FFMPEGSDK) + set (FFMPEGSDK_INC "${FFMPEGSDK}/include") + set (FFMPEGSDK_LIB "${FFMPEGSDK}/lib") + endif () + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake new file mode 100644 index 000000000..0dc02f5b6 --- /dev/null +++ b/cmake/FindSDL.cmake @@ -0,0 +1,177 @@ +# Locate SDL library +# This module defines +# SDL_LIBRARY, the name of the library to link against +# SDL_FOUND, if false, do not try to link to SDL +# SDL_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL_BUILDING_LIBRARY +# If this is defined, then no SDL_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDLmain which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration +# and no SDL_LIBRARY, it means CMake did not find your SDL library +# (SDL.dll, libsdl.so, SDL.framework, etc). +# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again. +# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL_LIBRARY +# variable, but when these values are unset, SDL_LIBRARY does not get created. +# +# +# $SDLDIR is an environment variable that would +# correspond to the ./configure --prefix=$SDLDIR +# used in building SDL. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDLmain which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PATH(SDL_INCLUDE_DIR SDL.h + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES include/SDL include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include/SDL12 + /usr/local/include/SDL11 # FreeBSD ports + /usr/include/SDL12 + /usr/include/SDL11 + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) +#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}") + +# SDL-1.1 is the name used by FreeBSD ports... +# don't confuse it for the version number. +FIND_LIBRARY(SDL_LIBRARY_TEMP + NAMES SDL SDL-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt +) + +#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}") + +IF(NOT SDL_BUILDING_LIBRARY) + IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDLmain for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDLMAIN_LIBRARY + NAMES SDLmain SDLmain-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL_BUILDING_LIBRARY) + +# SDL may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +SET(SDL_FOUND "NO") +IF(SDL_LIBRARY_TEMP) + # For SDLmain + IF(NOT SDL_BUILDING_LIBRARY) + IF(SDLMAIN_LIBRARY) + SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(SDLMAIN_LIBRARY) + ENDIF(NOT SDL_BUILDING_LIBRARY) + + # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "") + + SET(SDL_FOUND "YES") +ENDIF(SDL_LIBRARY_TEMP) + +#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}") + diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index e6f45fdb1..f66dbf2c4 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -2,7 +2,7 @@ macro (add_openmw_dir dir) set (files) foreach (u ${ARGN}) -file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp") +file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND OPENMW_FILES "${f}") @@ -14,7 +14,7 @@ endmacro (add_openmw_dir) macro (add_component_dir dir) set (files) foreach (u ${ARGN}) -file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp") +file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_FILES "${f}") @@ -23,9 +23,73 @@ endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) +macro (add_component_qt_dir dir) +set (files) +foreach (u ${ARGN}) +file (GLOB ALL "${dir}/${u}.[ch]pp") +foreach (f ${ALL}) +list (APPEND files "${f}") +list (APPEND COMPONENT_FILES "${f}") +endforeach (f) +file (GLOB MOC_H "${dir}/${u}.hpp") +foreach (fi ${MOC_H}) +list (APPEND COMPONENT_MOC_FILES "${fi}") +endforeach (fi) +endforeach (u) +source_group ("components\\${dir}" FILES ${files}) +endmacro (add_component_qt_dir) + macro (copy_all_files source_dir destination_dir files) foreach (f ${files}) get_filename_component(filename ${f} NAME) configure_file(${source_dir}/${f} ${destination_dir}/${filename} COPYONLY) endforeach (f) endmacro (copy_all_files) + +macro (add_file project type file) +list (APPEND ${project}${type} ${file}) +endmacro (add_file) + +macro (add_unit project dir unit) +add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") +add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") +endmacro (add_unit) + +macro (add_qt_unit project dir unit) +add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") +add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") +add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") +endmacro (add_qt_unit) + +macro (add_hdr project dir unit) +add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") +endmacro (add_hdr) + +macro (add_qt_hdr project dir unit) +add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") +add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") +endmacro (add_qt_hdr) + +macro (opencs_units dir) +foreach (u ${ARGN}) +add_qt_unit (OPENCS ${dir} ${u}) +endforeach (u) +endmacro (opencs_units) + +macro (opencs_units_noqt dir) +foreach (u ${ARGN}) +add_unit (OPENCS ${dir} ${u}) +endforeach (u) +endmacro (opencs_units_noqt) + +macro (opencs_hdrs dir) +foreach (u ${ARGN}) +add_qt_hdr (OPENCS ${dir} ${u}) +endforeach (u) +endmacro (opencs_hdrs) + +macro (opencs_hdrs_noqt dir) +foreach (u ${ARGN}) +add_hdr (OPENCS ${dir} ${u}) +endforeach (u) +endmacro (opencs_hdrs_noqt) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 29d6f46cd..38625fb52 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -15,15 +15,15 @@ add_component_dir (bsa ) add_component_dir (nif - controlled effect nif_types record controller extra node record_ptr data nif_file property + controlled effect niftypes record controller extra node record_ptr data niffile property ) add_component_dir (nifogre - ogre_nif_loader + ogrenifloader ) add_component_dir (nifbullet - bullet_nif_loader + bulletnifloader ) add_component_dir (to_utf8 @@ -39,7 +39,7 @@ add_component_dir (esm loadclas loadclot loadcont loadcrea loadcrec loaddial loaddoor loadench loadfact loadglob loadgmst loadinfo loadingr loadland loadlevlist loadligh loadlocks loadltex loadmgef loadmisc loadnpcc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat - loadweap records aipackage effectlist spelllist + loadweap records aipackage effectlist spelllist variant variantimp ) add_component_dir (misc @@ -48,7 +48,7 @@ add_component_dir (misc add_component_dir (files linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary ogreplugin + filelibrary ogreplugin constrainedfiledatastream lowlevelfile ) add_component_dir (compiler @@ -59,12 +59,28 @@ add_component_dir (compiler add_component_dir (interpreter context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes - miscopcodes opcodes runtime scriptopcodes spatialopcodes types + miscopcodes opcodes runtime scriptopcodes spatialopcodes types defines ) +add_component_dir (translation + translation + ) + +find_package(Qt4 COMPONENTS QtCore QtGui) + +if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) + add_component_qt_dir (fileorderlist + model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile + utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort + ) + + include(${QT_USE_FILE}) + QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) +endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) + include_directories(${BULLET_INCLUDE_DIRS}) -add_library(components STATIC ${COMPONENT_FILES}) +add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 8380b0838..c97ca4562 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -29,135 +29,71 @@ #include #include "bsa_file.hpp" -namespace -{ +#include "../files/constrainedfiledatastream.hpp" using namespace Ogre; -using namespace Bsa; - -struct ciLessBoost : std::binary_function -{ - bool operator() (const std::string & s1, const std::string & s2) const { - //case insensitive version of is_less - return boost::ilexicographical_compare(s1, s2); - } -}; - -struct pathComparer -{ -private: - std::string find; - -public: - pathComparer(const std::string& toFind) : find(toFind) { } - - bool operator() (const std::string& other) - { - return boost::iequals(find, other); - } -}; static bool fsstrict = false; -/// An OGRE Archive wrapping a BSAFile archive -class DirArchive: public Ogre::FileSystemArchive +static char strict_normalize_char(char ch) { - boost::filesystem::path currentdir; - std::map, ciLessBoost> m; - unsigned int cutoff; + return ch == '\\' ? '/' : ch; +} - bool findFile(const String& filename, std::string& copy) const +static char nonstrict_normalize_char(char ch) +{ + return ch == '\\' ? '/' : std::tolower(ch); +} + +template +static std::string normalize_path(T1 begin, T2 end) +{ + std::string normalized; + normalized.reserve(std::distance(begin, end)); + char (*normalize_char)(char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char; + std::transform(begin, end, std::back_inserter(normalized), normalize_char); + return normalized; +} + +/// An OGRE Archive wrapping a BSAFile archive +class DirArchive: public Ogre::Archive +{ + typedef std::map index; + + index mIndex; + + index::const_iterator lookup_filename (std::string const & filename) const { - copy = filename; - std::replace(copy.begin(), copy.end(), '\\', '/'); - - if(copy.at(0) == '/') - copy.erase(0, 1); - - if(fsstrict == true) - return true; - - std::string folder; - //int delimiter = 0; - size_t lastSlash = copy.rfind('/'); - if (lastSlash != std::string::npos) - { - folder = copy.substr(0, lastSlash); - //delimiter = lastSlash+1; - } - - std::vector current; - { - std::map,ciLessBoost>::const_iterator found = m.find(folder); - - if (found == m.end()) - { - return false; - } - else - current = found->second; - } - - std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, ciLessBoost()); - if (find != current.end() && !ciLessBoost()(copy, current.front())) - { - if (!boost::iequals(copy, *find)) - if ((find = std::find_if(current.begin(), current.end(), pathComparer(copy))) == current.end()) //\todo Check if this line is actually needed - return false; - - copy = *find; - return true; - } - - return false; + std::string normalized = normalize_path (filename.begin (), filename.end ()); + return mIndex.find (normalized); } - public: +public: DirArchive(const String& name) - : FileSystemArchive(name, "Dir"), currentdir (name) + : Archive(name, "Dir") { - mType = "Dir"; - std::string s = name; - cutoff = s.size() + 1; - if(fsstrict == false) - populateMap(currentdir); + typedef boost::filesystem::recursive_directory_iterator directory_iterator; - } - void populateMap(boost::filesystem::path d){ - //need to cut off first - boost::filesystem::directory_iterator dir_iter(d), dir_end; - std::vector filesind; - for(;dir_iter != dir_end; dir_iter++) - { - if(boost::filesystem::is_directory(*dir_iter)) - populateMap(*dir_iter); - else + directory_iterator end; + + size_t prefix = name.size (); + + if (name.size () > 0 && name [prefix - 1] != '\\' && name [prefix - 1] != '/') + ++prefix; + + for (directory_iterator i (name); i != end; ++i) { - std::string s = dir_iter->path().string(); - std::replace(s.begin(), s.end(), '\\', '/'); + if(boost::filesystem::is_directory (*i)) + continue; - std::string small; - if(cutoff < s.size()) - small = s.substr(cutoff, s.size() - cutoff); - else - small = s.substr(cutoff - 1, s.size() - cutoff); + std::string proper = i->path ().string (); - filesind.push_back(small); + std::string searchable = normalize_path (proper.begin () + prefix, proper.end ()); + + mIndex.insert (std::make_pair (searchable, proper)); } } - std::sort(filesind.begin(), filesind.end(), ciLessBoost()); - - std::string small; - std::string original = d.string(); - std::replace(original.begin(), original.end(), '\\', '/'); - if(cutoff < original.size()) - small = original.substr(cutoff, original.size() - cutoff); - else - small = original.substr(cutoff - 1, original.size() - cutoff); - - m[small] = filesind; - } bool isCaseSensitive() const { return fsstrict; } @@ -165,31 +101,106 @@ class DirArchive: public Ogre::FileSystemArchive void load() {} void unload() {} - bool exists(const String& filename) { - std::string copy; - - if (findFile(filename, copy)) - return FileSystemArchive::exists(copy); - - return false; - } - DataStreamPtr open(const String& filename, bool readonly = true) const - { - std::string copy; + { + index::const_iterator i = lookup_filename (filename); - if (findFile(filename, copy)) - return FileSystemArchive::open(copy, readonly); + if (i == mIndex.end ()) + { + std::ostringstream os; + os << "The file '" << filename << "' could not be found."; + throw std::runtime_error (os.str ()); + } - DataStreamPtr p; - return p; - } + return openConstrainedFileDataStream (i->second.c_str ()); + } + StringVectorPtr list(bool recursive = true, bool dirs = false) + { + return find ("*", recursive, dirs); + } + + FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) + { + return findFileInfo ("*", recursive, dirs); + } + + StringVectorPtr find(const String& pattern, bool recursive = true, + bool dirs = false) + { + std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); + StringVectorPtr ptr = StringVectorPtr(new StringVector()); + for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();iter++) + { + if(Ogre::StringUtil::match(iter->first, normalizedPattern) || + (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern))) + ptr->push_back(iter->first); + } + return ptr; + } + + bool exists(const String& filename) + { + return lookup_filename(filename) != mIndex.end (); + } + + time_t getModifiedTime(const String&) { return 0; } + + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) const + { + std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + + index::const_iterator i = mIndex.find(normalizedPattern); + if(i != mIndex.end()) + { + std::string::size_type pt = i->first.rfind('/'); + if(pt == std::string::npos) + pt = 0; + + FileInfo fi; + fi.archive = const_cast(this); + fi.path = i->first.substr(0, pt); + fi.filename = i->first.substr((i->first[pt]=='/') ? pt+1 : pt); + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + else + { + for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();iter++) + { + if(Ogre::StringUtil::match(iter->first, normalizedPattern) || + (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern))) + { + std::string::size_type pt = iter->first.rfind('/'); + if(pt == std::string::npos) + pt = 0; + + FileInfo fi; + fi.archive = const_cast(this); + fi.path = iter->first.substr(0, pt); + fi.filename = iter->first.substr((iter->first[pt]=='/') ? pt+1 : pt); + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + } + } + + return ptr; + } }; class BSAArchive : public Archive { - BSAFile arc; + Bsa::BSAFile arc; + + static const char *extractFilename(const Bsa::BSAFile::FileStruct &entry) + { + return entry.name; + } public: BSAArchive(const String& name) @@ -202,13 +213,13 @@ public: void load() {} void unload() {} - DataStreamPtr open(const String& filename, bool recursive = true) const + DataStreamPtr open(const String& filename, bool readonly = true) const { // Get a non-const reference to arc. This is a hack and it's all // OGRE's fault. You should NOT expect an open() command not to // have any side effects on the archive, and hence this function // should not have been declared const in the first place. - BSAFile *narc = const_cast(&arc); + Bsa::BSAFile *narc = const_cast(&arc); // Open the file return narc->getFile(filename.c_str()); @@ -218,93 +229,65 @@ public: return arc.exists(filename.c_str()); } - bool cexists(const String& filename) const { - return arc.exists(filename.c_str()); - } - time_t getModifiedTime(const String&) { return 0; } // This is never called as far as I can see. StringVectorPtr list(bool recursive = true, bool dirs = false) { - //std::cout << "list(" << recursive << ", " << dirs << ")\n"; - StringVectorPtr ptr = StringVectorPtr(new StringVector()); - return ptr; + return find ("*", recursive, dirs); } // Also never called. FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) { - //std::cout << "listFileInfo(" << recursive << ", " << dirs << ")\n"; - FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); - return ptr; + return findFileInfo ("*", recursive, dirs); } - // After load() is called, find("*") is called once. It doesn't seem - // to matter that we return an empty list, exists() gets called on - // the correct files anyway. - StringVectorPtr find(const String& pattern, bool recursive = true, - bool dirs = false) - { - //std::cout << "find(" << pattern << ", " << recursive - // << ", " << dirs << ")\n"; - StringVectorPtr ptr = StringVectorPtr(new StringVector()); - return ptr; - } + StringVectorPtr find(const String& pattern, bool recursive = true, + bool dirs = false) + { + std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); + const Bsa::BSAFile::FileList &filelist = arc.getList(); + StringVectorPtr ptr = StringVectorPtr(new StringVector()); + for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();iter++) + { + std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name)); + if(Ogre::StringUtil::match(ent, normalizedPattern) || + (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern))) + ptr->push_back(iter->name); + } + return ptr; + } - /* Gets called once for each of the ogre formats, *.program, - *.material etc. We ignore all these. + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) const + { + std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + const Bsa::BSAFile::FileList &filelist = arc.getList(); - However, it's also called by MyGUI to find individual textures, - and we can't ignore these since many of the GUI textures are - located in BSAs. So instead we channel it through exists() and - set up a single-element result list if the file is found. - */ - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const - { - FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();iter++) + { + std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name)); + if(Ogre::StringUtil::match(ent, normalizedPattern) || + (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern))) + { + std::string::size_type pt = ent.rfind('/'); + if(pt == std::string::npos) + pt = 0; - // Check if the file exists (only works for single files - wild - // cards and recursive search isn't implemented.) - if(cexists(pattern)) - { - FileInfo fi; - fi.archive = this; - fi.filename = pattern; - // It apparently doesn't matter that we return bogus - // information - fi.path = ""; - fi.compressedSize = fi.uncompressedSize = 0; + FileInfo fi; + fi.archive = const_cast(this); + fi.path = std::string(iter->name, pt); + fi.filename = std::string(iter->name + ((ent[pt]=='/') ? pt+1 : pt)); + fi.compressedSize = fi.uncompressedSize = iter->fileSize; - ptr->push_back(fi); - } + ptr->push_back(fi); + } + } - return ptr; - } - - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) - { - FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); - - // Check if the file exists (only works for single files - wild - // cards and recursive search isn't implemented.) - if(cexists(pattern)) - { - FileInfo fi; - fi.archive = this; - fi.filename = pattern; - // It apparently doesn't matter that we return bogus - // information - fi.path = ""; - fi.compressedSize = fi.uncompressedSize = 0; - - ptr->push_back(fi); - } - - return ptr; - } + return ptr; + } }; // An archive factory for BSA archives @@ -322,6 +305,11 @@ public: return new BSAArchive(name); } + virtual Archive* createInstance(const String& name, bool readOnly) + { + return new BSAArchive(name); + } + void destroyInstance( Archive* arch) { delete arch; } }; @@ -364,7 +352,6 @@ static void insertDirFactory() } } -} namespace Bsa { diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 1700e1aab..8db4fa888 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -24,93 +24,12 @@ #include "bsa_file.hpp" #include -#include -#include -#include +#include "../files/constrainedfiledatastream.hpp" using namespace std; using namespace Bsa; -class ConstrainedDataStream : public Ogre::DataStream { - std::ifstream mStream; - const size_t mStart; - size_t mPos; - bool mIsEOF; - -public: - ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) - : mStream(fname.c_str(), std::ios_base::binary), mStart(start), mPos(0), mIsEOF(false) - { - mSize = length; - if(!mStream.seekg(mStart, std::ios_base::beg)) - throw std::runtime_error("Error seeking to start of BSA entry"); - } - - ConstrainedDataStream(const Ogre::String &name, const Ogre::String &fname, - size_t start, size_t length) - : Ogre::DataStream(name), mStream(fname.c_str(), std::ios_base::binary), - mStart(start), mPos(0), mIsEOF(false) - { - mSize = length; - if(!mStream.seekg(mStart, std::ios_base::beg)) - throw std::runtime_error("Error seeking to start of BSA entry"); - } - - - virtual size_t read(void *buf, size_t count) - { - mStream.clear(); - - if(count > mSize-mPos) - { - count = mSize-mPos; - mIsEOF = true; - } - mStream.read(reinterpret_cast(buf), count); - - count = mStream.gcount(); - mPos += count; - return count; - } - - virtual void skip(long count) - { - if((count >= 0 && (size_t)count <= mSize-mPos) || - (count < 0 && (size_t)-count <= mPos)) - { - mStream.clear(); - if(mStream.seekg(count, std::ios_base::cur)) - { - mPos += count; - mIsEOF = false; - } - } - } - - virtual void seek(size_t pos) - { - if(pos < mSize) - { - mStream.clear(); - if(mStream.seekg(pos+mStart, std::ios_base::beg)) - { - mPos = pos; - mIsEOF = false; - } - } - } - - virtual size_t tell() const - { return mPos; } - - virtual bool eof() const - { return mIsEOF; } - - virtual void close() - { mStream.close(); } -}; - /// Error handling void BSAFile::fail(const string &msg) @@ -253,5 +172,5 @@ Ogre::DataStreamPtr BSAFile::getFile(const char *file) fail("File not found: " + string(file)); const FileStruct &fs = files[i]; - return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize)); + return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); } diff --git a/components/bsa/tests/Makefile b/components/bsa/tests/Makefile index bc2bf4e50..73e20d7b3 100644 --- a/components/bsa/tests/Makefile +++ b/components/bsa/tests/Makefile @@ -1,6 +1,6 @@ GCC=g++ -all: bsa_file_test bsatool ogre_archive_test +all: bsa_file_test ogre_archive_test I_OGRE=$(shell pkg-config --cflags OGRE) L_OGRE=$(shell pkg-config --libs OGRE) @@ -11,12 +11,5 @@ bsa_file_test: bsa_file_test.cpp ../bsa_file.cpp ogre_archive_test: ogre_archive_test.cpp ../bsa_file.cpp ../bsa_archive.cpp $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) -bsatool: bsatool.cpp ../bsa_file.cpp bsatool_cmd.c - $(GCC) $^ -o $@ - -bsatool_cmd.c: bsatool.ggo - gengetopt < bsatool.ggo - clean: rm *_test - rm bsatool diff --git a/components/bsa/tests/bsatool.cpp b/components/bsa/tests/bsatool.cpp deleted file mode 100644 index df37e3827..000000000 --- a/components/bsa/tests/bsatool.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "../bsa_file.hpp" - -#include "bsatool_cmd.h" - -#include -#include -#include -#include - -#include "../../mangle/stream/filters/buffer_stream.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Bsa; - -int main(int argc, char** argv) -{ - gengetopt_args_info info; - - if(cmdline_parser(argc, argv, &info) != 0) - return 1; - - if(info.inputs_num != 1) - { - if(info.inputs_num == 0) - cout << "ERROR: missing BSA file\n\n"; - else - cout << "ERROR: more than one BSA file specified\n\n"; - cmdline_parser_print_help(); - return 1; - } - - // Open file - BSAFile bsa; - char *arcname = info.inputs[0]; - try { bsa.open(arcname); } - catch(exception &e) - { - cout << "ERROR reading BSA archive '" << arcname - << "'\nDetails:\n" << e.what() << endl; - return 2; - } - - if(info.extract_given) - { - char *file = info.extract_arg; - - if(!bsa.exists(file)) - { - cout << "ERROR: file '" << file << "' not found\n"; - cout << "In archive: " << arcname << endl; - return 3; - } - - // Find the base name of the file - int pos = strlen(file); - while(pos > 0 && file[pos] != '\\') pos--; - char *base = file+pos+1; - - // TODO: We might add full directory name extraction later. We - // could also allow automatic conversion from / to \ in - // parameter file names. - - // Load the file into a memory buffer - BufferStream data(bsa.getFile(file)); - - // Write the file to disk - ofstream out(base, ios::binary); - out.write((char*)data.getPtr(), data.size()); - out.close(); - - return 0; - } - - // List all files - const BSAFile::FileList &files = bsa.getList(); - for(int i=0; i" -#description "" -args "--unamed-opts=BSA-FILE -F bsatool_cmd -G" - -option "extract" x "Extract file from archive" string optional -option "long" l "Include extra information in archive listing" optional - -text "\nIf no option is given, the default action is to list all files in the archive." diff --git a/components/bsa/tests/bsatool_cmd.c b/components/bsa/tests/bsatool_cmd.c deleted file mode 100644 index caa8cd720..000000000 --- a/components/bsa/tests/bsatool_cmd.c +++ /dev/null @@ -1,1146 +0,0 @@ -/* - File autogenerated by gengetopt version 2.22.2 - generated with the following command: - gengetopt --unamed-opts=BSA-FILE -F bsatool_cmd -G - - The developers of gengetopt consider the fixed text that goes in all - gengetopt output files to be in the public domain: - we make no copyright claims on it. -*/ - -/* If we use autoconf. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include - -#ifndef FIX_UNUSED -#define FIX_UNUSED(X) (void) (X) /* avoid warnings for unused params */ -#endif - - -#include "bsatool_cmd.h" - -const char *gengetopt_args_info_purpose = "Inspect and extract files from Bethesda BSA archives"; - -const char *gengetopt_args_info_usage = "Usage: bsatool [OPTIONS]... [BSA-FILE]..."; - -const char *gengetopt_args_info_description = ""; - -const char *gengetopt_args_info_help[] = { - " -h, --help Print help and exit", - " -V, --version Print version and exit", - " -x, --extract=STRING Extract file from archive", - " -l, --long Include extra information in archive listing", - "\nIf no option is given, the default action is to list all files in the archive.", - 0 -}; - -typedef enum {ARG_NO - , ARG_STRING -} cmdline_parser_arg_type; - -static -void clear_given (struct gengetopt_args_info *args_info); -static -void clear_args (struct gengetopt_args_info *args_info); - -static int -cmdline_parser_internal (int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params, const char *additional_error); - - -static char * -gengetopt_strdup (const char *s); - -static -void clear_given (struct gengetopt_args_info *args_info) -{ - args_info->help_given = 0 ; - args_info->version_given = 0 ; - args_info->extract_given = 0 ; - args_info->long_given = 0 ; -} - -static -void clear_args (struct gengetopt_args_info *args_info) -{ - FIX_UNUSED (args_info); - args_info->extract_arg = NULL; - args_info->extract_orig = NULL; - -} - -static -void init_args_info(struct gengetopt_args_info *args_info) -{ - - - args_info->help_help = gengetopt_args_info_help[0] ; - args_info->version_help = gengetopt_args_info_help[1] ; - args_info->extract_help = gengetopt_args_info_help[2] ; - args_info->long_help = gengetopt_args_info_help[3] ; - -} - -void -cmdline_parser_print_version (void) -{ - printf ("%s %s\n", - (strlen(CMDLINE_PARSER_PACKAGE_NAME) ? CMDLINE_PARSER_PACKAGE_NAME : CMDLINE_PARSER_PACKAGE), - CMDLINE_PARSER_VERSION); -} - -static void print_help_common(void) { - cmdline_parser_print_version (); - - if (strlen(gengetopt_args_info_purpose) > 0) - printf("\n%s\n", gengetopt_args_info_purpose); - - if (strlen(gengetopt_args_info_usage) > 0) - printf("\n%s\n", gengetopt_args_info_usage); - - printf("\n"); - - if (strlen(gengetopt_args_info_description) > 0) - printf("%s\n\n", gengetopt_args_info_description); -} - -void -cmdline_parser_print_help (void) -{ - int i = 0; - print_help_common(); - while (gengetopt_args_info_help[i]) - printf("%s\n", gengetopt_args_info_help[i++]); -} - -void -cmdline_parser_init (struct gengetopt_args_info *args_info) -{ - clear_given (args_info); - clear_args (args_info); - init_args_info (args_info); - - args_info->inputs = 0; - args_info->inputs_num = 0; -} - -void -cmdline_parser_params_init(struct cmdline_parser_params *params) -{ - if (params) - { - params->override = 0; - params->initialize = 1; - params->check_required = 1; - params->check_ambiguity = 0; - params->print_errors = 1; - } -} - -struct cmdline_parser_params * -cmdline_parser_params_create(void) -{ - struct cmdline_parser_params *params = - (struct cmdline_parser_params *)malloc(sizeof(struct cmdline_parser_params)); - cmdline_parser_params_init(params); - return params; -} - -static void -free_string_field (char **s) -{ - if (*s) - { - free (*s); - *s = 0; - } -} - - -static void -cmdline_parser_release (struct gengetopt_args_info *args_info) -{ - unsigned int i; - free_string_field (&(args_info->extract_arg)); - free_string_field (&(args_info->extract_orig)); - - - for (i = 0; i < args_info->inputs_num; ++i) - free (args_info->inputs [i]); - - if (args_info->inputs_num) - free (args_info->inputs); - - clear_given (args_info); -} - - -static void -write_into_file(FILE *outfile, const char *opt, const char *arg, const char *values[]) -{ - FIX_UNUSED (values); - if (arg) { - fprintf(outfile, "%s=\"%s\"\n", opt, arg); - } else { - fprintf(outfile, "%s\n", opt); - } -} - - -int -cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info) -{ - int i = 0; - - if (!outfile) - { - fprintf (stderr, "%s: cannot dump options to stream\n", CMDLINE_PARSER_PACKAGE); - return EXIT_FAILURE; - } - - if (args_info->help_given) - write_into_file(outfile, "help", 0, 0 ); - if (args_info->version_given) - write_into_file(outfile, "version", 0, 0 ); - if (args_info->extract_given) - write_into_file(outfile, "extract", args_info->extract_orig, 0); - if (args_info->long_given) - write_into_file(outfile, "long", 0, 0 ); - - - i = EXIT_SUCCESS; - return i; -} - -int -cmdline_parser_file_save(const char *filename, struct gengetopt_args_info *args_info) -{ - FILE *outfile; - int i = 0; - - outfile = fopen(filename, "w"); - - if (!outfile) - { - fprintf (stderr, "%s: cannot open file for writing: %s\n", CMDLINE_PARSER_PACKAGE, filename); - return EXIT_FAILURE; - } - - i = cmdline_parser_dump(outfile, args_info); - fclose (outfile); - - return i; -} - -void -cmdline_parser_free (struct gengetopt_args_info *args_info) -{ - cmdline_parser_release (args_info); -} - -/** @brief replacement of strdup, which is not standard */ -char * -gengetopt_strdup (const char *s) -{ - char *result = 0; - if (!s) - return result; - - result = (char*)malloc(strlen(s) + 1); - if (result == (char*)0) - return (char*)0; - strcpy(result, s); - return result; -} - -int -cmdline_parser (int argc, char * const *argv, struct gengetopt_args_info *args_info) -{ - return cmdline_parser2 (argc, argv, args_info, 0, 1, 1); -} - -int -cmdline_parser_ext (int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params) -{ - int result; - result = cmdline_parser_internal (argc, argv, args_info, params, 0); - - if (result == EXIT_FAILURE) - { - cmdline_parser_free (args_info); - exit (EXIT_FAILURE); - } - - return result; -} - -int -cmdline_parser2 (int argc, char * const *argv, struct gengetopt_args_info *args_info, int override, int initialize, int check_required) -{ - int result; - struct cmdline_parser_params params; - - params.override = override; - params.initialize = initialize; - params.check_required = check_required; - params.check_ambiguity = 0; - params.print_errors = 1; - - result = cmdline_parser_internal (argc, argv, args_info, ¶ms, 0); - - if (result == EXIT_FAILURE) - { - cmdline_parser_free (args_info); - exit (EXIT_FAILURE); - } - - return result; -} - -int -cmdline_parser_required (struct gengetopt_args_info *args_info, const char *prog_name) -{ - FIX_UNUSED (args_info); - FIX_UNUSED (prog_name); - return EXIT_SUCCESS; -} - -/* - * Extracted from the glibc source tree, version 2.3.6 - * - * Licensed under the GPL as per the whole glibc source tree. - * - * This file was modified so that getopt_long can be called - * many times without risking previous memory to be spoiled. - * - * Modified by Andre Noll and Lorenzo Bettini for use in - * GNU gengetopt generated files. - * - */ - -/* - * we must include anything we need since this file is not thought to be - * inserted in a file already using getopt.h - * - * Lorenzo - */ - -struct option -{ - const char *name; - /* has_arg can't be an enum because some compilers complain about - type mismatches in all the code that assumes it is an int. */ - int has_arg; - int *flag; - int val; -}; - -/* This version of `getopt' appears to the caller like standard Unix `getopt' - but it behaves differently for the user, since it allows the user - to intersperse the options with the other arguments. - - As `getopt' works, it permutes the elements of ARGV so that, - when it is done, all the options precede everything else. Thus - all application programs are extended to handle flexible argument order. -*/ -/* - If the field `flag' is not NULL, it points to a variable that is set - to the value given in the field `val' when the option is found, but - left unchanged if the option is not found. - - To have a long-named option do something other than set an `int' to - a compiled-in constant, such as set a value from `custom_optarg', set the - option's `flag' field to zero and its `val' field to a nonzero - value (the equivalent single-letter option character, if there is - one). For long options that have a zero `flag' field, `getopt' - returns the contents of the `val' field. */ - -/* Names for the values of the `has_arg' field of `struct option'. */ -#ifndef no_argument -#define no_argument 0 -#endif - -#ifndef required_argument -#define required_argument 1 -#endif - -#ifndef optional_argument -#define optional_argument 2 -#endif - -struct custom_getopt_data { - /* - * These have exactly the same meaning as the corresponding global variables, - * except that they are used for the reentrant versions of getopt. - */ - int custom_optind; - int custom_opterr; - int custom_optopt; - char *custom_optarg; - - /* True if the internal members have been initialized. */ - int initialized; - - /* - * The next char to be scanned in the option-element in which the last option - * character we returned was found. This allows us to pick up the scan where - * we left off. If this is zero, or a null string, it means resume the scan by - * advancing to the next ARGV-element. - */ - char *nextchar; - - /* - * Describe the part of ARGV that contains non-options that have been skipped. - * `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is - * the index after the last of them. - */ - int first_nonopt; - int last_nonopt; -}; - -/* - * the variables optarg, optind, opterr and optopt are renamed with - * the custom_ prefix so that they don't interfere with getopt ones. - * - * Moreover they're static so they are visible only from within the - * file where this very file will be included. - */ - -/* - * For communication from `custom_getopt' to the caller. When `custom_getopt' finds an - * option that takes an argument, the argument value is returned here. - */ -static char *custom_optarg; - -/* - * Index in ARGV of the next element to be scanned. This is used for - * communication to and from the caller and for communication between - * successive calls to `custom_getopt'. - * - * On entry to `custom_getopt', 1 means this is the first call; initialize. - * - * When `custom_getopt' returns -1, this is the index of the first of the non-option - * elements that the caller should itself scan. - * - * Otherwise, `custom_optind' communicates from one call to the next how much of ARGV - * has been scanned so far. - * - * 1003.2 says this must be 1 before any call. - */ -static int custom_optind = 1; - -/* - * Callers store zero here to inhibit the error message for unrecognized - * options. - */ -static int custom_opterr = 1; - -/* - * Set to an option character which was unrecognized. This must be initialized - * on some systems to avoid linking in the system's own getopt implementation. - */ -static int custom_optopt = '?'; - -/* - * Exchange two adjacent subsequences of ARGV. One subsequence is elements - * [first_nonopt,last_nonopt) which contains all the non-options that have been - * skipped so far. The other is elements [last_nonopt,custom_optind), which contains - * all the options processed since those non-options were skipped. - * `first_nonopt' and `last_nonopt' are relocated so that they describe the new - * indices of the non-options in ARGV after they are moved. - */ -static void exchange(char **argv, struct custom_getopt_data *d) -{ - int bottom = d->first_nonopt; - int middle = d->last_nonopt; - int top = d->custom_optind; - char *tem; - - /* - * Exchange the shorter segment with the far end of the longer segment. - * That puts the shorter segment into the right place. It leaves the - * longer segment in the right place overall, but it consists of two - * parts that need to be swapped next. - */ - while (top > middle && middle > bottom) { - if (top - middle > middle - bottom) { - /* Bottom segment is the short one. */ - int len = middle - bottom; - int i; - - /* Swap it with the top part of the top segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = - argv[top - (middle - bottom) + i]; - argv[top - (middle - bottom) + i] = tem; - } - /* Exclude the moved bottom segment from further swapping. */ - top -= len; - } else { - /* Top segment is the short one. */ - int len = top - middle; - int i; - - /* Swap it with the bottom part of the bottom segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = argv[middle + i]; - argv[middle + i] = tem; - } - /* Exclude the moved top segment from further swapping. */ - bottom += len; - } - } - /* Update records for the slots the non-options now occupy. */ - d->first_nonopt += (d->custom_optind - d->last_nonopt); - d->last_nonopt = d->custom_optind; -} - -/* Initialize the internal data when the first call is made. */ -static void custom_getopt_initialize(struct custom_getopt_data *d) -{ - /* - * Start processing options with ARGV-element 1 (since ARGV-element 0 - * is the program name); the sequence of previously skipped non-option - * ARGV-elements is empty. - */ - d->first_nonopt = d->last_nonopt = d->custom_optind; - d->nextchar = NULL; - d->initialized = 1; -} - -#define NONOPTION_P (argv[d->custom_optind][0] != '-' || argv[d->custom_optind][1] == '\0') - -/* return: zero: continue, nonzero: return given value to user */ -static int shuffle_argv(int argc, char *const *argv,const struct option *longopts, - struct custom_getopt_data *d) -{ - /* - * Give FIRST_NONOPT & LAST_NONOPT rational values if CUSTOM_OPTIND has been - * moved back by the user (who may also have changed the arguments). - */ - if (d->last_nonopt > d->custom_optind) - d->last_nonopt = d->custom_optind; - if (d->first_nonopt > d->custom_optind) - d->first_nonopt = d->custom_optind; - /* - * If we have just processed some options following some - * non-options, exchange them so that the options come first. - */ - if (d->first_nonopt != d->last_nonopt && - d->last_nonopt != d->custom_optind) - exchange((char **) argv, d); - else if (d->last_nonopt != d->custom_optind) - d->first_nonopt = d->custom_optind; - /* - * Skip any additional non-options and extend the range of - * non-options previously skipped. - */ - while (d->custom_optind < argc && NONOPTION_P) - d->custom_optind++; - d->last_nonopt = d->custom_optind; - /* - * The special ARGV-element `--' means premature end of options. Skip - * it like a null option, then exchange with previous non-options as if - * it were an option, then skip everything else like a non-option. - */ - if (d->custom_optind != argc && !strcmp(argv[d->custom_optind], "--")) { - d->custom_optind++; - if (d->first_nonopt != d->last_nonopt - && d->last_nonopt != d->custom_optind) - exchange((char **) argv, d); - else if (d->first_nonopt == d->last_nonopt) - d->first_nonopt = d->custom_optind; - d->last_nonopt = argc; - d->custom_optind = argc; - } - /* - * If we have done all the ARGV-elements, stop the scan and back over - * any non-options that we skipped and permuted. - */ - if (d->custom_optind == argc) { - /* - * Set the next-arg-index to point at the non-options that we - * previously skipped, so the caller will digest them. - */ - if (d->first_nonopt != d->last_nonopt) - d->custom_optind = d->first_nonopt; - return -1; - } - /* - * If we have come to a non-option and did not permute it, either stop - * the scan or describe it to the caller and pass it by. - */ - if (NONOPTION_P) { - d->custom_optarg = argv[d->custom_optind++]; - return 1; - } - /* - * We have found another option-ARGV-element. Skip the initial - * punctuation. - */ - d->nextchar = (argv[d->custom_optind] + 1 + (longopts != NULL && argv[d->custom_optind][1] == '-')); - return 0; -} - -/* - * Check whether the ARGV-element is a long option. - * - * If there's a long option "fubar" and the ARGV-element is "-fu", consider - * that an abbreviation of the long option, just like "--fu", and not "-f" with - * arg "u". - * - * This distinction seems to be the most useful approach. - * - */ -static int check_long_opt(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind, - int print_errors, struct custom_getopt_data *d) -{ - char *nameend; - const struct option *p; - const struct option *pfound = NULL; - int exact = 0; - int ambig = 0; - int indfound = -1; - int option_index; - - for (nameend = d->nextchar; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; - - /* Test all long options for either exact match or abbreviated matches */ - for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp(p->name, d->nextchar, nameend - d->nextchar)) { - if ((unsigned int) (nameend - d->nextchar) - == (unsigned int) strlen(p->name)) { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } else if (pfound == NULL) { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } else if (pfound->has_arg != p->has_arg - || pfound->flag != p->flag - || pfound->val != p->val) - /* Second or later nonexact match found. */ - ambig = 1; - } - if (ambig && !exact) { - if (print_errors) { - fprintf(stderr, - "%s: option `%s' is ambiguous\n", - argv[0], argv[d->custom_optind]); - } - d->nextchar += strlen(d->nextchar); - d->custom_optind++; - d->custom_optopt = 0; - return '?'; - } - if (pfound) { - option_index = indfound; - d->custom_optind++; - if (*nameend) { - if (pfound->has_arg != no_argument) - d->custom_optarg = nameend + 1; - else { - if (print_errors) { - if (argv[d->custom_optind - 1][1] == '-') { - /* --option */ - fprintf(stderr, "%s: option `--%s' doesn't allow an argument\n", - argv[0], pfound->name); - } else { - /* +option or -option */ - fprintf(stderr, "%s: option `%c%s' doesn't allow an argument\n", - argv[0], argv[d->custom_optind - 1][0], pfound->name); - } - - } - d->nextchar += strlen(d->nextchar); - d->custom_optopt = pfound->val; - return '?'; - } - } else if (pfound->has_arg == required_argument) { - if (d->custom_optind < argc) - d->custom_optarg = argv[d->custom_optind++]; - else { - if (print_errors) { - fprintf(stderr, - "%s: option `%s' requires an argument\n", - argv[0], - argv[d->custom_optind - 1]); - } - d->nextchar += strlen(d->nextchar); - d->custom_optopt = pfound->val; - return optstring[0] == ':' ? ':' : '?'; - } - } - d->nextchar += strlen(d->nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } - /* - * Can't find it as a long option. If this is not getopt_long_only, or - * the option starts with '--' or is not a valid short option, then - * it's an error. Otherwise interpret it as a short option. - */ - if (print_errors) { - if (argv[d->custom_optind][1] == '-') { - /* --option */ - fprintf(stderr, - "%s: unrecognized option `--%s'\n", - argv[0], d->nextchar); - } else { - /* +option or -option */ - fprintf(stderr, - "%s: unrecognized option `%c%s'\n", - argv[0], argv[d->custom_optind][0], - d->nextchar); - } - } - d->nextchar = (char *) ""; - d->custom_optind++; - d->custom_optopt = 0; - return '?'; -} - -static int check_short_opt(int argc, char *const *argv, const char *optstring, - int print_errors, struct custom_getopt_data *d) -{ - char c = *d->nextchar++; - const char *temp = strchr(optstring, c); - - /* Increment `custom_optind' when we start to process its last character. */ - if (*d->nextchar == '\0') - ++d->custom_optind; - if (!temp || c == ':') { - if (print_errors) - fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c); - - d->custom_optopt = c; - return '?'; - } - if (temp[1] == ':') { - if (temp[2] == ':') { - /* This is an option that accepts an argument optionally. */ - if (*d->nextchar != '\0') { - d->custom_optarg = d->nextchar; - d->custom_optind++; - } else - d->custom_optarg = NULL; - d->nextchar = NULL; - } else { - /* This is an option that requires an argument. */ - if (*d->nextchar != '\0') { - d->custom_optarg = d->nextchar; - /* - * If we end this ARGV-element by taking the - * rest as an arg, we must advance to the next - * element now. - */ - d->custom_optind++; - } else if (d->custom_optind == argc) { - if (print_errors) { - fprintf(stderr, - "%s: option requires an argument -- %c\n", - argv[0], c); - } - d->custom_optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - } else - /* - * We already incremented `custom_optind' once; - * increment it again when taking next ARGV-elt - * as argument. - */ - d->custom_optarg = argv[d->custom_optind++]; - d->nextchar = NULL; - } - } - return c; -} - -/* - * Scan elements of ARGV for option characters given in OPTSTRING. - * - * If an element of ARGV starts with '-', and is not exactly "-" or "--", - * then it is an option element. The characters of this element - * (aside from the initial '-') are option characters. If `getopt' - * is called repeatedly, it returns successively each of the option characters - * from each of the option elements. - * - * If `getopt' finds another option character, it returns that character, - * updating `custom_optind' and `nextchar' so that the next call to `getopt' can - * resume the scan with the following option character or ARGV-element. - * - * If there are no more option characters, `getopt' returns -1. - * Then `custom_optind' is the index in ARGV of the first ARGV-element - * that is not an option. (The ARGV-elements have been permuted - * so that those that are not options now come last.) - * - * OPTSTRING is a string containing the legitimate option characters. - * If an option character is seen that is not listed in OPTSTRING, - * return '?' after printing an error message. If you set `custom_opterr' to - * zero, the error message is suppressed but we still return '?'. - * - * If a char in OPTSTRING is followed by a colon, that means it wants an arg, - * so the following text in the same ARGV-element, or the text of the following - * ARGV-element, is returned in `custom_optarg'. Two colons mean an option that - * wants an optional arg; if there is text in the current ARGV-element, - * it is returned in `custom_optarg', otherwise `custom_optarg' is set to zero. - * - * If OPTSTRING starts with `-' or `+', it requests different methods of - * handling the non-option ARGV-elements. - * See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. - * - * Long-named options begin with `--' instead of `-'. - * Their names may be abbreviated as long as the abbreviation is unique - * or is an exact match for some defined option. If they have an - * argument, it follows the option name in the same ARGV-element, separated - * from the option name by a `=', or else the in next ARGV-element. - * When `getopt' finds a long-named option, it returns 0 if that option's - * `flag' field is nonzero, the value of the option's `val' field - * if the `flag' field is zero. - * - * The elements of ARGV aren't really const, because we permute them. - * But we pretend they're const in the prototype to be compatible - * with other systems. - * - * LONGOPTS is a vector of `struct option' terminated by an - * element containing a name which is zero. - * - * LONGIND returns the index in LONGOPT of the long-named option found. - * It is only valid when a long-named option has been found by the most - * recent call. - * - * Return the option character from OPTS just read. Return -1 when there are - * no more options. For unrecognized options, or options missing arguments, - * `custom_optopt' is set to the option letter, and '?' is returned. - * - * The OPTS string is a list of characters which are recognized option letters, - * optionally followed by colons, specifying that that letter takes an - * argument, to be placed in `custom_optarg'. - * - * If a letter in OPTS is followed by two colons, its argument is optional. - * This behavior is specific to the GNU `getopt'. - * - * The argument `--' causes premature termination of argument scanning, - * explicitly telling `getopt' that there are no more options. If OPTS begins - * with `--', then non-option arguments are treated as arguments to the option - * '\0'. This behavior is specific to the GNU `getopt'. - */ - -static int getopt_internal_r(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind, - struct custom_getopt_data *d) -{ - int ret, print_errors = d->custom_opterr; - - if (optstring[0] == ':') - print_errors = 0; - if (argc < 1) - return -1; - d->custom_optarg = NULL; - - /* - * This is a big difference with GNU getopt, since optind == 0 - * means initialization while here 1 means first call. - */ - if (d->custom_optind == 0 || !d->initialized) { - if (d->custom_optind == 0) - d->custom_optind = 1; /* Don't scan ARGV[0], the program name. */ - custom_getopt_initialize(d); - } - if (d->nextchar == NULL || *d->nextchar == '\0') { - ret = shuffle_argv(argc, argv, longopts, d); - if (ret) - return ret; - } - if (longopts && (argv[d->custom_optind][1] == '-' )) - return check_long_opt(argc, argv, optstring, longopts, - longind, print_errors, d); - return check_short_opt(argc, argv, optstring, print_errors, d); -} - -static int custom_getopt_internal(int argc, char *const *argv, const char *optstring, - const struct option *longopts, int *longind) -{ - int result; - /* Keep a global copy of all internal members of d */ - static struct custom_getopt_data d; - - d.custom_optind = custom_optind; - d.custom_opterr = custom_opterr; - result = getopt_internal_r(argc, argv, optstring, longopts, - longind, &d); - custom_optind = d.custom_optind; - custom_optarg = d.custom_optarg; - custom_optopt = d.custom_optopt; - return result; -} - -static int custom_getopt_long (int argc, char *const *argv, const char *options, - const struct option *long_options, int *opt_index) -{ - return custom_getopt_internal(argc, argv, options, long_options, - opt_index); -} - - -static char *package_name = 0; - -/** - * @brief updates an option - * @param field the generic pointer to the field to update - * @param orig_field the pointer to the orig field - * @param field_given the pointer to the number of occurrence of this option - * @param prev_given the pointer to the number of occurrence already seen - * @param value the argument for this option (if null no arg was specified) - * @param possible_values the possible values for this option (if specified) - * @param default_value the default value (in case the option only accepts fixed values) - * @param arg_type the type of this option - * @param check_ambiguity @see cmdline_parser_params.check_ambiguity - * @param override @see cmdline_parser_params.override - * @param no_free whether to free a possible previous value - * @param multiple_option whether this is a multiple option - * @param long_opt the corresponding long option - * @param short_opt the corresponding short option (or '-' if none) - * @param additional_error possible further error specification - */ -static -int update_arg(void *field, char **orig_field, - unsigned int *field_given, unsigned int *prev_given, - char *value, const char *possible_values[], - const char *default_value, - cmdline_parser_arg_type arg_type, - int check_ambiguity, int override, - int no_free, int multiple_option, - const char *long_opt, char short_opt, - const char *additional_error) -{ - FIX_UNUSED (field); - char *stop_char = 0; - const char *val = value; - int found; - char **string_field; - - stop_char = 0; - found = 0; - - if (!multiple_option && prev_given && (*prev_given || (check_ambiguity && *field_given))) - { - if (short_opt != '-') - fprintf (stderr, "%s: `--%s' (`-%c') option given more than once%s\n", - package_name, long_opt, short_opt, - (additional_error ? additional_error : "")); - else - fprintf (stderr, "%s: `--%s' option given more than once%s\n", - package_name, long_opt, - (additional_error ? additional_error : "")); - return 1; /* failure */ - } - - FIX_UNUSED (default_value); - - if (field_given && *field_given && ! override) - return 0; - if (prev_given) - (*prev_given)++; - if (field_given) - (*field_given)++; - if (possible_values) - val = possible_values[found]; - - switch(arg_type) { - case ARG_STRING: - if (val) { - string_field = (char **)field; - if (!no_free && *string_field) - free (*string_field); /* free previous string */ - *string_field = gengetopt_strdup (val); - } - break; - default: - break; - }; - - - /* store the original value */ - switch(arg_type) { - case ARG_NO: - break; - default: - if (value && orig_field) { - if (no_free) { - *orig_field = value; - } else { - if (*orig_field) - free (*orig_field); /* free previous string */ - *orig_field = gengetopt_strdup (value); - } - } - }; - - return 0; /* OK */ -} - - -int -cmdline_parser_internal ( - int argc, char * const *argv, struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params, const char *additional_error) -{ - int c; /* Character of the parsed option. */ - - int error = 0; - struct gengetopt_args_info local_args_info; - - int override; - int initialize; - int check_required; - int check_ambiguity; - - char *optarg; - int optind; - int opterr; - int optopt; - - package_name = argv[0]; - - override = params->override; - initialize = params->initialize; - check_required = params->check_required; - check_ambiguity = params->check_ambiguity; - - if (initialize) - cmdline_parser_init (args_info); - - cmdline_parser_init (&local_args_info); - - optarg = 0; - optind = 0; - opterr = params->print_errors; - optopt = '?'; - - while (1) - { - int option_index = 0; - - static struct option long_options[] = { - { "help", 0, NULL, 'h' }, - { "version", 0, NULL, 'V' }, - { "extract", 1, NULL, 'x' }, - { "long", 0, NULL, 'l' }, - { 0, 0, 0, 0 } - }; - - custom_optarg = optarg; - custom_optind = optind; - custom_opterr = opterr; - custom_optopt = optopt; - - c = custom_getopt_long (argc, argv, "hVx:l", long_options, &option_index); - - optarg = custom_optarg; - optind = custom_optind; - opterr = custom_opterr; - optopt = custom_optopt; - - if (c == -1) break; /* Exit from `while (1)' loop. */ - - switch (c) - { - case 'h': /* Print help and exit. */ - cmdline_parser_print_help (); - cmdline_parser_free (&local_args_info); - exit (EXIT_SUCCESS); - - case 'V': /* Print version and exit. */ - cmdline_parser_print_version (); - cmdline_parser_free (&local_args_info); - exit (EXIT_SUCCESS); - - case 'x': /* Extract file from archive. */ - - - if (update_arg( (void *)&(args_info->extract_arg), - &(args_info->extract_orig), &(args_info->extract_given), - &(local_args_info.extract_given), optarg, 0, 0, ARG_STRING, - check_ambiguity, override, 0, 0, - "extract", 'x', - additional_error)) - goto failure; - - break; - case 'l': /* Include extra information in archive listing. */ - - - if (update_arg( 0 , - 0 , &(args_info->long_given), - &(local_args_info.long_given), optarg, 0, 0, ARG_NO, - check_ambiguity, override, 0, 0, - "long", 'l', - additional_error)) - goto failure; - - break; - - case 0: /* Long option with no short option */ - case '?': /* Invalid option. */ - /* `getopt_long' already printed an error message. */ - goto failure; - - default: /* bug: option not considered. */ - fprintf (stderr, "%s: option unknown: %c%s\n", CMDLINE_PARSER_PACKAGE, c, (additional_error ? additional_error : "")); - abort (); - } /* switch */ - } /* while */ - - - - - cmdline_parser_release (&local_args_info); - - if ( error ) - return (EXIT_FAILURE); - - if (optind < argc) - { - int i = 0 ; - int found_prog_name = 0; - /* whether program name, i.e., argv[0], is in the remaining args - (this may happen with some implementations of getopt, - but surely not with the one included by gengetopt) */ - - - args_info->inputs_num = argc - optind - found_prog_name; - args_info->inputs = - (char **)(malloc ((args_info->inputs_num)*sizeof(char *))) ; - while (optind < argc) - args_info->inputs[ i++ ] = gengetopt_strdup (argv[optind++]) ; - } - - return 0; - -failure: - - cmdline_parser_release (&local_args_info); - return (EXIT_FAILURE); -} diff --git a/components/bsa/tests/bsatool_cmd.h b/components/bsa/tests/bsatool_cmd.h deleted file mode 100644 index 98fe2633f..000000000 --- a/components/bsa/tests/bsatool_cmd.h +++ /dev/null @@ -1,179 +0,0 @@ -/** @file bsatool_cmd.h - * @brief The header file for the command line option parser - * generated by GNU Gengetopt version 2.22.2 - * http://www.gnu.org/software/gengetopt. - * DO NOT modify this file, since it can be overwritten - * @author GNU Gengetopt by Lorenzo Bettini */ - -#ifndef BSATOOL_CMD_H -#define BSATOOL_CMD_H - -/* If we use autoconf. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include /* for FILE */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#ifndef CMDLINE_PARSER_PACKAGE -/** @brief the program name (used for printing errors) */ -#define CMDLINE_PARSER_PACKAGE "bsatool" -#endif - -#ifndef CMDLINE_PARSER_PACKAGE_NAME -/** @brief the complete program name (used for help and version) */ -#define CMDLINE_PARSER_PACKAGE_NAME "bsatool" -#endif - -#ifndef CMDLINE_PARSER_VERSION -/** @brief the program version */ -#define CMDLINE_PARSER_VERSION "1.0" -#endif - -/** @brief Where the command line options are stored */ -struct gengetopt_args_info -{ - const char *help_help; /**< @brief Print help and exit help description. */ - const char *version_help; /**< @brief Print version and exit help description. */ - char * extract_arg; /**< @brief Extract file from archive. */ - char * extract_orig; /**< @brief Extract file from archive original value given at command line. */ - const char *extract_help; /**< @brief Extract file from archive help description. */ - const char *long_help; /**< @brief Include extra information in archive listing help description. */ - - unsigned int help_given ; /**< @brief Whether help was given. */ - unsigned int version_given ; /**< @brief Whether version was given. */ - unsigned int extract_given ; /**< @brief Whether extract was given. */ - unsigned int long_given ; /**< @brief Whether long was given. */ - - char **inputs ; /**< @brief unamed options (options without names) */ - unsigned inputs_num ; /**< @brief unamed options number */ -} ; - -/** @brief The additional parameters to pass to parser functions */ -struct cmdline_parser_params -{ - int override; /**< @brief whether to override possibly already present options (default 0) */ - int initialize; /**< @brief whether to initialize the option structure gengetopt_args_info (default 1) */ - int check_required; /**< @brief whether to check that all required options were provided (default 1) */ - int check_ambiguity; /**< @brief whether to check for options already specified in the option structure gengetopt_args_info (default 0) */ - int print_errors; /**< @brief whether getopt_long should print an error message for a bad option (default 1) */ -} ; - -/** @brief the purpose string of the program */ -extern const char *gengetopt_args_info_purpose; -/** @brief the usage string of the program */ -extern const char *gengetopt_args_info_usage; -/** @brief all the lines making the help output */ -extern const char *gengetopt_args_info_help[]; - -/** - * The command line parser - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser (int argc, char * const *argv, - struct gengetopt_args_info *args_info); - -/** - * The command line parser (version with additional parameters - deprecated) - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @param override whether to override possibly already present options - * @param initialize whether to initialize the option structure my_args_info - * @param check_required whether to check that all required options were provided - * @return 0 if everything went fine, NON 0 if an error took place - * @deprecated use cmdline_parser_ext() instead - */ -int cmdline_parser2 (int argc, char * const *argv, - struct gengetopt_args_info *args_info, - int override, int initialize, int check_required); - -/** - * The command line parser (version with additional parameters) - * @param argc the number of command line options - * @param argv the command line options - * @param args_info the structure where option information will be stored - * @param params additional parameters for the parser - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_ext (int argc, char * const *argv, - struct gengetopt_args_info *args_info, - struct cmdline_parser_params *params); - -/** - * Save the contents of the option struct into an already open FILE stream. - * @param outfile the stream where to dump options - * @param args_info the option struct to dump - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_dump(FILE *outfile, - struct gengetopt_args_info *args_info); - -/** - * Save the contents of the option struct into a (text) file. - * This file can be read by the config file parser (if generated by gengetopt) - * @param filename the file where to save - * @param args_info the option struct to save - * @return 0 if everything went fine, NON 0 if an error took place - */ -int cmdline_parser_file_save(const char *filename, - struct gengetopt_args_info *args_info); - -/** - * Print the help - */ -void cmdline_parser_print_help(void); -/** - * Print the version - */ -void cmdline_parser_print_version(void); - -/** - * Initializes all the fields a cmdline_parser_params structure - * to their default values - * @param params the structure to initialize - */ -void cmdline_parser_params_init(struct cmdline_parser_params *params); - -/** - * Allocates dynamically a cmdline_parser_params structure and initializes - * all its fields to their default values - * @return the created and initialized cmdline_parser_params structure - */ -struct cmdline_parser_params *cmdline_parser_params_create(void); - -/** - * Initializes the passed gengetopt_args_info structure's fields - * (also set default values for options that have a default) - * @param args_info the structure to initialize - */ -void cmdline_parser_init (struct gengetopt_args_info *args_info); -/** - * Deallocates the string fields of the gengetopt_args_info structure - * (but does not deallocate the structure itself) - * @param args_info the structure to deallocate - */ -void cmdline_parser_free (struct gengetopt_args_info *args_info); - -/** - * Checks that all the required options were specified - * @param args_info the structure to check - * @param prog_name the name of the program that will be used to print - * possible errors - * @return - */ -int cmdline_parser_required (struct gengetopt_args_info *args_info, - const char *prog_name); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* BSATOOL_CMD_H */ diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 52192625b..027f3de71 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -14,6 +14,7 @@ #include "stringparser.hpp" #include "extensions.hpp" #include "context.hpp" +#include namespace Compiler { @@ -199,8 +200,8 @@ namespace Compiler { mMemberOp = false; - std::string name2 = toLower (name); - std::string id = toLower (mExplicit); + std::string name2 = Misc::StringUtils::lowerCase (name); + std::string id = Misc::StringUtils::lowerCase (mExplicit); char type = getContext().getMemberType (name2, id); @@ -285,7 +286,7 @@ namespace Compiler { start(); - std::string name2 = toLower (name); + std::string name2 = Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 9b184f1ff..98be2d3d1 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -45,7 +45,10 @@ namespace Compiler reportWarning ("Names for script " + mName + " do not match", loc); mState = EndCompleteState; - return true; + return false; // we are stopping here, because there might be more garbage on the end line, + // that we must ignore. + // + /// \todo allow this workaround to be disabled for newer scripts } return Parser::parseName (name, loc, scanner); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index a4cbc1ffe..210d18dc9 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -8,6 +8,7 @@ #include "locals.hpp" #include "generator.hpp" #include "extensions.hpp" +#include namespace Compiler { @@ -91,13 +92,13 @@ namespace Compiler return false; } - std::string name2 = toLower (name); + std::string name2 = Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); if (type!=' ') { - getErrorHandler().error ("can't re-declare local variable", loc); + getErrorHandler().error ("catoLowern't re-declare local variable", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; @@ -112,7 +113,7 @@ namespace Compiler if (mState==SetState) { - std::string name2 = toLower (name); + std::string name2 = Misc::StringUtils::lowerCase (name); mName = name2; // local variable? @@ -138,7 +139,7 @@ namespace Compiler if (mState==SetMemberVarState) { - mMemberName = toLower (name); + mMemberName = Misc::StringUtils::lowerCase (name); char type = getContext().getMemberType (mMemberName, mName); if (type!=' ') @@ -205,13 +206,13 @@ namespace Compiler if (mState==BeginState && getContext().isId (name)) { mState = PotentialExplicitState; - mExplicit = toLower (name); + mExplicit = Misc::StringUtils::lowerCase (name); return true; } if (mState==BeginState && mAllowExpression) { - std::string name2 = toLower (name); + std::string name2 = Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 90368eee0..896458e48 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -9,6 +9,8 @@ #include "exception.hpp" #include "scanner.hpp" +#include + namespace Compiler { // Report the error and throw an exception. @@ -57,10 +59,7 @@ namespace Compiler std::string Parser::toLower (const std::string& name) { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + std::string lowerCase = Misc::StringUtils::lowerCase(name); return lowerCase; } diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 962699dfa..7f43c36a5 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -11,6 +11,8 @@ #include "parser.hpp" #include "extensions.hpp" +#include + namespace Compiler { bool Scanner::get (char& c) @@ -268,11 +270,7 @@ namespace Compiler int i = 0; - std::string lowerCase; - lowerCase.reserve (name.size()); - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); + std::string lowerCase = Misc::StringUtils::lowerCase(name); for (; keywords[i]; ++i) if (lowerCase==keywords[i]) diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index 396a88c78..09c902131 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -6,6 +6,7 @@ #include "scanner.hpp" #include "generator.hpp" +#include namespace Compiler { @@ -22,7 +23,7 @@ namespace Compiler { start(); if (mSmashCase) - Generator::pushString (mCode, mLiterals, toLower (name)); + Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); else Generator::pushString (mCode, mLiterals, name); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index aa870f925..bd86f9ba0 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -9,18 +9,6 @@ namespace ESM // Pixel color value. Standard four-byte rr,gg,bb,aa format. typedef int32_t Color; -enum VarType -{ - VT_Unknown, - VT_None, - VT_Short, - VT_Int, - VT_Long, - VT_Float, - VT_String, - VT_Ignored -}; - enum Specialization { SPC_Combat = 0, diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index e0c5c08af..335d12337 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -89,6 +89,7 @@ struct MasterData { std::string name; uint64_t size; + int index; // Position of the parent file in the global list of loaded files }; // Data that is only present in save game files @@ -113,6 +114,10 @@ struct ESM_Context size_t leftFile; NAME recName, subName; HEDRstruct header; + // When working with multiple esX files, we will generate lists of all files that + // actually contribute to a specific cell. Therefore, we need to store the index + // of the file belonging to this contest. See CellStore::(list/load)refs for details. + int index; // True if subName has been read but not used. bool subCached; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2915a1ce7..b4f581d7e 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -1,6 +1,8 @@ #include "esmreader.hpp" #include +#include "../files/constrainedfiledatastream.hpp" + namespace ESM { @@ -13,6 +15,11 @@ ESM_Context ESMReader::getContext() return mCtx; } +ESMReader::ESMReader(void): + mBuffer(50*1024) +{ +} + void ESMReader::restoreContext(const ESM_Context &rc) { // Reopen the file if necessary @@ -70,8 +77,7 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) // Get the header getHNT(mCtx.header, "HEDR", 300); - if (mCtx.header.version != VER_12 && mCtx.header.version != VER_13) - fail("Unsupported file format version"); + // Some mods abuse the header.version field for the version of the mod instead of the version of the file format, so we can only ignore it. while (isNextSub("MAST")) { @@ -108,16 +114,12 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us - open(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); + open (openConstrainedFileDataStream (file.c_str ()), file); } void ESMReader::openRaw(const std::string &file) { - std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us - openRaw(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); + openRaw (openConstrainedFileDataStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) @@ -325,11 +327,21 @@ void ESMReader::getExact(void*x, int size) std::string ESMReader::getString(int size) { - char *ptr = ToUTF8::getBuffer(size); - mEsm->read(ptr, size); + size_t s = size; + if (mBuffer.size() <= s) + // Add some extra padding to reduce the chance of having to resize + // again later. + mBuffer.resize(3*s); + + // And make sure the string is zero terminated + mBuffer[s] = 0; + + // read ESM data + char *ptr = &mBuffer[0]; + getExact(ptr, size); // Convert to UTF8 and return - return ToUTF8::getUtf8(mEncoding); + return mEncoder->getUtf8(ptr, size); } void ESMReader::fail(const std::string &msg) @@ -347,21 +359,9 @@ void ESMReader::fail(const std::string &msg) throw std::runtime_error(ss.str()); } -void ESMReader::setEncoding(const std::string& encoding) +void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) { - if (encoding == "win1250") - { - mEncoding = ToUTF8::WINDOWS_1250; - } - else if (encoding == "win1251") - { - mEncoding = ToUTF8::WINDOWS_1251; - } - else - { - // Default Latin encoding - mEncoding = ToUTF8::WINDOWS_1252; - } + mEncoder = encoder; } } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 6a74c53e8..ae876edf8 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -20,6 +20,8 @@ class ESMReader { public: + ESMReader(void); + /************************************************************************* * * Public type definitions @@ -35,14 +37,14 @@ public: *************************************************************************/ int getVer() const { return mCtx.header.version; } - float getFVer() { if(mCtx.header.version == VER_12) return 1.2; else return 1.3; } - int getSpecial() { return mSpf; } - int getType() { return mCtx.header.type; } - const std::string getAuthor() { return mCtx.header.author.toString(); } - const std::string getDesc() { return mCtx.header.desc.toString(); } + float getFVer() const { if(mCtx.header.version == VER_12) return 1.2; else return 1.3; } + int getSpecial() const { return mSpf; } + int getType() const { return mCtx.header.type; } + const std::string getAuthor() const { return mCtx.header.author.toString(); } + const std::string getDesc() const { return mCtx.header.desc.toString(); } const SaveData &getSaveData() const { return mSaveData; } - const MasterList &getMasters() { return mMasters; } - const NAME &retSubName() { return mCtx.subName; } + const MasterList &getMasters() const { return mMasters; } + const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } /************************************************************************* @@ -76,6 +78,17 @@ public: void openRaw(const std::string &file); + // This is a quick hack for multiple esm/esp files. Each plugin introduces its own + // terrain palette, but ESMReader does not pass a reference to the correct plugin + // to the individual load() methods. This hack allows to pass this reference + // indirectly to the load() method. + int mIdx; + void setIndex(const int index) {mIdx = index; mCtx.index = index;} + const int getIndex() {return mIdx;} + + void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} + std::vector *getGlobalReaderList() {return mGlobalReaderList;} + /************************************************************************* * * Medium-level reading shortcuts @@ -108,6 +121,14 @@ public: getHT(x); } + template + void getHNOT(X &x, const char* name, int size) + { + assert(sizeof(X) == size); + if(isNextSub(name)) + getHT(x); + } + int64_t getHNLong(const char *name); // Get data of a given type/size, including subrecord header @@ -233,8 +254,8 @@ public: /// Used for error handling void fail(const std::string &msg); - /// Sets font encoding for ESM strings - void setEncoding(const std::string& encoding); + /// Sets font encoder for ESM strings + void setEncoder(ToUTF8::Utf8Encoder* encoder); private: Ogre::DataStreamPtr mEsm; @@ -244,9 +265,13 @@ private: // Special file signifier (see SpecialFile enum above) int mSpf; + // Buffer for ESM strings + std::vector mBuffer; + SaveData mSaveData; MasterList mMasters; - ToUTF8::FromType mEncoding; + std::vector *mGlobalReaderList; + ToUTF8::Utf8Encoder* mEncoder; }; } #endif diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index c1ae06490..b9dd5b57b 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -136,15 +136,15 @@ void ESMWriter::writeHNString(const std::string& name, const std::string& data) endRecord(name); } -void ESMWriter::writeHNString(const std::string& name, const std::string& data, int size) +void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) { - assert(static_cast (data.size()) <= size); + assert(data.size() <= size); startSubRecord(name); writeHString(data); - if (static_cast (data.size()) < size) + if (data.size() < size) { - for (int i = data.size(); i < size; ++i) + for (size_t i = data.size(); i < size; ++i) write("\0",1); } @@ -157,12 +157,8 @@ void ESMWriter::writeHString(const std::string& data) write("\0", 1); else { - char *ptr = ToUTF8::getBuffer(data.size()+1); - strncpy(ptr, &data[0], data.size()); - ptr[data.size()] = '\0'; - // Convert to UTF8 and return - std::string ascii = ToUTF8::getLegacyEnc(m_encoding); + std::string ascii = m_encoder->getLegacyEnc(data); write(ascii.c_str(), ascii.size()); } @@ -181,7 +177,7 @@ void ESMWriter::writeName(const std::string& name) write(name.c_str(), name.size()); } -void ESMWriter::write(const char* data, int size) +void ESMWriter::write(const char* data, size_t size) { if (count && !m_records.empty()) { @@ -192,21 +188,9 @@ void ESMWriter::write(const char* data, int size) m_stream->write(data, size); } -void ESMWriter::setEncoding(const std::string& encoding) +void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) { - if (encoding == "win1250") - { - m_encoding = ToUTF8::WINDOWS_1250; - } - else if (encoding == "win1251") - { - m_encoding = ToUTF8::WINDOWS_1251; - } - else - { - // Default Latin encoding - m_encoding = ToUTF8::WINDOWS_1252; - } + m_encoder = encoder; } } diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index d3777be81..94e173b4c 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -6,7 +6,7 @@ #include #include "esmcommon.hpp" -#include "../to_utf8/to_utf8.hpp" +#include namespace ESM { @@ -16,7 +16,7 @@ class ESMWriter { std::string name; std::streampos position; - int size; + size_t size; }; public: @@ -24,7 +24,7 @@ public: void setVersion(int ver); int getType(); void setType(int type); - void setEncoding(const std::string& encoding); // Write strings as UTF-8? + void setEncoder(ToUTF8::Utf8Encoder *encoding); // Write strings as UTF-8? void setAuthor(const std::string& author); void setDescription(const std::string& desc); @@ -35,7 +35,7 @@ public: void close(); void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, int size); + void writeHNString(const std::string& name, const std::string& data, size_t size); void writeHNCString(const std::string& name, const std::string& data) { startSubRecord(name); @@ -76,7 +76,7 @@ public: } template - void writeT(const T& data, int size) + void writeT(const T& data, size_t size) { write((char*)&data, size); } @@ -87,18 +87,17 @@ public: void writeHString(const std::string& data); void writeHCString(const std::string& data); void writeName(const std::string& data); - void write(const char* data, int size); + void write(const char* data, size_t size); private: std::list m_masters; std::list m_records; std::ostream* m_stream; std::streampos m_headerPos; - ToUTF8::FromType m_encoding; + ToUTF8::Utf8Encoder* m_encoder; int m_recordCount; HEDRstruct m_header; - SaveData m_saveData; }; } diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index f5e7e10e1..643fefda5 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -7,12 +7,27 @@ namespace ESM { void Apparatus::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); - esm.getHNT(mData, "AADT", 16); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNString("ITEX"); + // we will not treat duplicated subrecords as errors here + while (esm.hasMoreSubs()) + { + esm.getSubName(); + NAME subName = esm.retSubName(); + + if (subName == "MODL") + mModel = esm.getHString(); + else if (subName == "FNAM") + mName = esm.getHString(); + else if (subName == "AADT") + esm.getHT(mData); + else if (subName == "SCRI") + mScript = esm.getHString(); + else if (subName == "ITEX") + mIcon = esm.getHString(); + else + esm.fail("wrong subrecord type " + subName.toString() + " for APPA record"); + } } + void Apparatus::save(ESMWriter &esm) { esm.writeHNCString("MODL", mModel); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 97ce17775..76a48e5ec 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -2,13 +2,29 @@ #include #include +#include +#include #include "esmreader.hpp" #include "esmwriter.hpp" +#include +#include + namespace ESM { +/// Some overloaded copare operators. +bool operator==(const MovedCellRef& ref, int pRefnum) +{ + return (ref.mRefnum == pRefnum); +} + +bool operator==(const CellRef& ref, int pRefnum) +{ + return (ref.mRefnum == pRefnum); +} + void CellRef::save(ESMWriter &esm) { esm.writeHNT("FRMR", mRefnum); @@ -63,10 +79,11 @@ void CellRef::save(ESMWriter &esm) } } -void Cell::load(ESMReader &esm) +void Cell::load(ESMReader &esm, bool saveContext) { // Ignore this for now, it might mean we should delete the entire // cell? + // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! if (esm.isNextSub("DELE")) { esm.skipHSub(); } @@ -95,8 +112,8 @@ void Cell::load(ESMReader &esm) // instead. if (mData.mFlags & QuasiEx) mRegion = esm.getHNOString("RGNN"); - else - esm.getHNT(mAmbi, "AMBI", 16); + else if (esm.isNextSub("AMBI")) + esm.getHT(mAmbi); } else { @@ -110,8 +127,46 @@ void Cell::load(ESMReader &esm) esm.getHT(mNAM0); } + if (saveContext) { + mContextList.push_back(esm.getContext()); + esm.skipRecord(); + } +} + +void Cell::load(ESMReader &esm, MWWorld::ESMStore &store) +{ + this->load(esm, false); + + // preload moved references + while (esm.isNextSub("MVRF")) { + CellRef ref; + MovedCellRef cMRef; + getNextMVRF(esm, cMRef); + + MWWorld::Store &cStore = const_cast&>(store.get()); + ESM::Cell *cellAlt = const_cast(cStore.searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + + // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following + // implementation when the oher implementation works as well. + getNextRef(esm, ref); + std::string lowerCase; + + std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + mMovedRefs.push_back(cMRef); + // But there may be duplicates here! + ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum); + if (iter == cellAlt->mLeasedRefs.end()) + cellAlt->mLeasedRefs.push_back(ref); + else + *iter = ref; + } + // Save position of the cell references and move on - mContext = esm.getContext(); + mContextList.push_back(esm.getContext()); esm.skipRecord(); } @@ -141,14 +196,14 @@ void Cell::save(ESMWriter &esm) if (mMapColor != 0) esm.writeHNT("NAM5", mMapColor); } - + if (mNAM0 != 0) esm.writeHNT("NAM0", mNAM0); } -void Cell::restore(ESMReader &esm) const +void Cell::restore(ESMReader &esm, int iCtx) const { - esm.restoreContext(mContext); + esm.restoreContext(mContextList[iCtx]); } std::string Cell::getDescription() const @@ -167,17 +222,61 @@ std::string Cell::getDescription() const bool Cell::getNextRef(ESMReader &esm, CellRef &ref) { + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) return false; + // NOTE: We should not need this check. It is a safety check until we have checked + // more plugins, and how they treat these moved references. + if (esm.isNextSub("MVRF")) { + esm.skipRecord(); // skip MVRF + esm.skipRecord(); // skip CNDT + // That should be it, I haven't seen any other fields yet. + } + esm.getHNT(ref.mRefnum, "FRMR"); ref.mRefID = esm.getHNString("NAME"); + // Identify references belonging to a parent file and adapt the ID accordingly. + int local = (ref.mRefnum & 0xff000000) >> 24; + size_t global = esm.getIndex() + 1; + if (local) + { + // If the most significant 8 bits are used, then this reference already exists. + // In this case, do not spawn a new reference, but overwrite the old one. + ref.mRefnum &= 0x00ffffff; // delete old plugin ID + const ESM::ESMReader::MasterList &masters = esm.getMasters(); + global = masters[local-1].index + 1; + ref.mRefnum |= global << 24; // insert global plugin ID + } + else + { + // This is an addition by the present plugin. Set the corresponding plugin index. + ref.mRefnum |= global << 24; // insert global plugin ID + } + // getHNOT will not change the existing value if the subrecord is // missing ref.mScale = 1.0; esm.getHNOT(ref.mScale, "XSCL"); + // TODO: support loading references from saves, there are tons of keys not recognized yet. + // The following is just an incomplete list. + if (esm.isNextSub("ACTN")) + esm.skipHSub(); + if (esm.isNextSub("STPR")) + esm.skipHSub(); + if (esm.isNextSub("ACDT")) + esm.skipHSub(); + if (esm.isNextSub("ACSC")) + esm.skipHSub(); + if (esm.isNextSub("ACSL")) + esm.skipHSub(); + if (esm.isNextSub("CHRD")) + esm.skipHSub(); + else if (esm.isNextSub("CRED")) // ??? + esm.skipHSub(); + ref.mOwner = esm.getHNOString("ANAM"); ref.mGlob = esm.getHNOString("BNAM"); ref.mSoul = esm.getHNOString("XSOL"); @@ -215,11 +314,14 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) esm.getHNOT(ref.mUnam, "UNAM"); esm.getHNOT(ref.mFltv, "FLTV"); - esm.getHNT(ref.mPos, "DATA", 24); + esm.getHNOT(ref.mPos, "DATA", 24); // Number of references in the cell? Maximum once in each cell, // but not always at the beginning, and not always right. In other // words, completely useless. + // Update: Well, maybe not completely useless. This might actually be + // number_of_references + number_of_references_moved_here_Across_boundaries, + // and could be helpful for collecting these weird moved references. ref.mNam0 = 0; if (esm.isNextSub("NAM0")) { @@ -227,6 +329,29 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) //esm.getHNOT(NAM0, "NAM0"); } + if (esm.isNextSub("DELE")) { + esm.skipHSub(); + ref.mDeleted = 2; // Deleted, will not respawn. + // TODO: find out when references do respawn. + } else + ref.mDeleted = 0; + + return true; +} + +bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) +{ + esm.getHT(mref.mRefnum); + esm.getHNOT(mref.mTarget, "CNDT"); + + // Identify references belonging to a parent file and adapt the ID accordingly. + int local = (mref.mRefnum & 0xff000000) >> 24; + size_t global = esm.getIndex() + 1; + mref.mRefnum &= 0x00ffffff; // delete old plugin ID + const ESM::ESMReader::MasterList &masters = esm.getMasters(); + global = masters[local-1].index + 1; + mref.mRefnum |= global << 24; // insert global plugin ID + return true; } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index fbd7c0456..7db6dbe77 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -2,10 +2,19 @@ #define OPENMW_ESM_CELL_H #include +#include +#include #include "esmcommon.hpp" #include "defs.hpp" + +namespace MWWorld +{ + class ESMStore; +} + + namespace ESM { @@ -68,7 +77,10 @@ public: std::string mKey, mTrap; // Key and trap ID names, if any // No idea - occurs ONCE in Morrowind.esm, for an activator - char mUnam; + signed char mUnam; + + // Track deleted references. 0 - not deleted, 1 - deleted, but respawns, 2 - deleted and does not respawn. + int mDeleted; // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza // Brindisi Dorom", where it has the value 100. Also only for @@ -82,6 +94,31 @@ public: void save(ESMWriter &esm); }; +/* Moved cell reference tracking object. This mainly stores the target cell + of the reference, so we can easily know where it has been moved when another + plugin tries to move it independently. + Unfortunately, we need to implement this here. + */ +class MovedCellRef +{ +public: + int mRefnum; + + // Target cell (if exterior) + int mTarget[2]; + + // TODO: Support moving references between exterior and interior cells! + // This may happen in saves, when an NPC follows the player. Tribunal + // introduces a henchman (which no one uses), so we may need this as well. +}; + +/// Overloaded copare operator used to search inside a list of cell refs. +bool operator==(const MovedCellRef& ref, int pRefnum); +bool operator==(const CellRef& ref, int pRefnum); + +typedef std::list MovedCellRefTracker; +typedef std::list CellRefTracker; + /* Cells hold data about objects, creatures, statics (rocks, walls, buildings) and landscape (for exterior cells). Cells frequently also has other associated LAND and PGRD records. Combined, all this @@ -120,7 +157,7 @@ struct Cell // Optional region name for exterior and quasi-exterior cells. std::string mRegion; - ESM_Context mContext; // File position + std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; AMBIstruct mAmbi; float mWater; // Water level @@ -128,7 +165,16 @@ struct Cell int mMapColor; int mNAM0; - void load(ESMReader &esm); + // References "leased" from another cell (i.e. a different cell + // introduced this ref, and it has been moved here by a plugin) + CellRefTracker mLeasedRefs; + MovedCellRefTracker mMovedRefs; + + void load(ESMReader &esm, MWWorld::ESMStore &store); + + // This method is left in for compatibility with esmtool. Parsing moved references currently requires + // passing ESMStore, bit it does not know about this parameter, so we do it this way. + void load(ESMReader &esm, bool saveContext = true); void save(ESMWriter &esm); bool isExterior() const @@ -151,7 +197,7 @@ struct Cell // somewhere other than the file system, you need to pre-open the // ESMReader, and the filename must match the stored filename // exactly. - void restore(ESMReader &esm) const; + void restore(ESMReader &esm, int iCtx) const; std::string getDescription() const; ///< Return a short string describing the cell (mostly used for debugging/logging purpose) @@ -163,6 +209,11 @@ struct Cell reuse one memory location without blanking it between calls. */ static bool getNextRef(ESMReader &esm, CellRef &ref); + + /* This fetches an MVRF record, which is used to track moved references. + * Since they are comparably rare, we use a separate method for this. + */ + static bool getNextMVRF(ESMReader &esm, MovedCellRef &mref); }; } #endif diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 078c78811..61f3f763d 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -30,7 +30,7 @@ struct Dialogue }; std::string mId; - char mType; + signed char mType; std::vector mInfo; void load(ESMReader &esm); diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 39c07fb31..ccb519acd 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,47 +1,24 @@ #include "loadglob.hpp" -#include "esmreader.hpp" -#include "esmwriter.hpp" - namespace ESM { - -void Global::load(ESMReader &esm) -{ - std::string tmp = esm.getHNString("FNAM"); - if (tmp == "s") - mType = VT_Short; - else if (tmp == "l") - mType = VT_Int; - else if (tmp == "f") - mType = VT_Float; - else - esm.fail("Illegal global variable type " + tmp); - - // Note: Both floats and longs are represented as floats. - esm.getHNT(mValue, "FLTV"); -} - -void Global::save(ESMWriter &esm) -{ - switch(mType) + void Global::load (ESMReader &esm) { - case VT_Short: - esm.writeHNString("FNAM", "s"); - break; - - case VT_Int: - esm.writeHNString("FNAM", "l"); - break; - - case VT_Float: - esm.writeHNString("FNAM", "f"); - break; - - default: - return; + mValue.read (esm, ESM::Variant::Format_Global); } - esm.writeHNT("FLTV", mValue); -} + void Global::save (ESMWriter &esm) + { + mValue.write (esm, ESM::Variant::Format_Global); + } + + void Global::blank() + { + mValue.setType (ESM::VT_None); + } + + bool operator== (const Global& left, const Global& right) + { + return left.mId==right.mId && left.mValue==right.mValue; + } } diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 302729d2e..72e16c0ce 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -3,7 +3,7 @@ #include -#include "defs.hpp" +#include "variant.hpp" namespace ESM { @@ -18,11 +18,16 @@ class ESMWriter; struct Global { std::string mId; - unsigned mValue; - VarType mType; + Variant mValue; void load(ESMReader &esm); void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID). }; + +bool operator== (const Global& left, const Global& right); + } #endif diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 14e29f4ae..fe1cc1b04 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,222 +1,39 @@ #include "loadgmst.hpp" -#include - -#include - -#include "esmreader.hpp" -#include "esmwriter.hpp" - namespace ESM { - -/// \todo Review GMST "fixing". Probably remove completely or at least make it optional. Its definitely not -/// working properly in its current state and I doubt it can be fixed without breaking other stuff. - -// Some handy macros -#define cI(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mI == (x)); } -#define cF(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mF == (x)); } -#define cS(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mStr == (x)); } - -bool GameSetting::isDirtyTribunal() -{ - /* - Here, mId contains the game setting name, and we check the - setting for certain values. If it matches, this is a "mDirty" - entry. The correct entry (as defined in Tribunal and Bloodmoon - esms) are given in the comments. Many of the values are correct, - and are marked as 'same'. We still ignore them though, as they - are still in the wrong file and might override custom values - from other mods. - */ - - std::string label; - // Strings - cS("sProfitValue", "Profit Value"); // 'Profit:' - cS("sEditNote", "Edit Note"); // same - cS("sDeleteNote", "Delete Note?"); // same - cS("sMaxSale", "Max Sale"); // 'Seller Max' - cS("sMagicFabricantID", "Fabricant"); // 'Fabricant_summon' - cS("sTeleportDisabled", - "Teleportation magic does not work here.");// same - cS("sLevitateDisabled", - "Levitation magic does not work here."); // same - cS("sCompanionShare", "Companion Share"); // 'Share' - cS("sCompanionWarningButtonOne", - "Let the mercenary quit."); // same - cS("sCompanionWarningButtonTwo", - "Return to Companion Share display."); // same - cS("sCompanionWarningMessage", - "Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); - // 'Your mercenary is poorer now than when he contracted with - // you. Your mercenary will quit if you do not give him gold - // or goods to bring his Profit to a positive value.' - // [The difference here is "Profit Value" -> "Profit"] - - // Strings that matches the mId - cS("sEffectSummonFabricant", "sEffectSummonFabricant");// 'Summon Fabricant' - return false; -} - -// Bloodmoon variant -bool GameSetting::isDirtyBloodmoon() -{ - std::string label; - // Strings - cS("sWerewolfPopup", "Werewolf"); // same - cS("sWerewolfRestMessage", - "You cannot rest in werewolf form."); // same - cS("sWerewolfRefusal", - "You cannot do this as a werewolf."); // same - cS("sWerewolfAlarmMessage", - "You have been detected changing from a werewolf state."); - // 'You have been detected as a known werewolf.' - - // Strings that matches the mId - cS("sMagicCreature01ID", "sMagicCreature01ID"); // 'BM_wolf_grey_summon' - cS("sMagicCreature02ID", "sMagicCreature02ID"); // 'BM_bear_black_summon' - cS("sMagicCreature03ID", "sMagicCreature03ID"); // 'BM_wolf_bone_summon' - cS("sMagicCreature04ID", "sMagicCreature04ID"); // same - cS("sMagicCreature05ID", "sMagicCreature05ID"); // same - cS("sEffectSummonCreature01", "sEffectSummonCreature01"); // 'Calf Wolf' - cS("sEffectSummonCreature02", "sEffectSummonCreature02"); // 'Calf Bear' - cS("sEffectSummonCreature03", "sEffectSummonCreature03"); // 'Summon Bonewolf' - cS("sEffectSummonCreature04", "sEffectSummonCreature04"); // same - cS("sEffectSummonCreature05", "sEffectSummonCreature05"); // same - - // Integers - cI("iWereWolfBounty", 10000); // 1000 - cI("iWereWolfFightMod", 100); // same - cI("iWereWolfFleeMod", 100); // same - cI("iWereWolfLevelToAttack", 20); // same - - // Floats - cF("fFleeDistance", 3000); // same - cF("fCombatDistanceWerewolfMod", 0.3); // same - cF("fWereWolfFatigue", 400); // same - cF("fWereWolfEnchant", 1); // 0 - cF("fWereWolfArmorer", 1); // 0 - cF("fWereWolfBlock", 1); // 0 - cF("fWereWolfSneak", 1); // 95 - cF("fWereWolfDestruction", 1); // 0 - cF("fWereWolfEndurance", 150); // same - cF("fWereWolfConjuration", 1); // 0 - cF("fWereWolfRestoration", 1); // 0 - cF("fWereWolfAthletics", 150); // 50 - cF("fWereWolfLuck", 1); // 25 - cF("fWereWolfSilverWeaponDamageMult", 1.5); // 2 - cF("fWereWolfMediumArmor", 1); // 0 - cF("fWereWolfShortBlade", 1); // 0 - cF("fWereWolfAcrobatics", 150); // 80 - cF("fWereWolfSpeechcraft", 1); // 0 - cF("fWereWolfAlteration", 1); // 0 - cF("fWereWolfIllusion", 1); // 0 - cF("fWereWolfLongBlade", 1); // 0 - cF("fWereWolfMarksman", 1); // 0 - cF("fWereWolfHandtoHand", 100); // same - cF("fWereWolfIntellegence", 1); // 0 - cF("fWereWolfAlchemy", 1); // 0 - cF("fWereWolfUnarmored", 100); // same - cF("fWereWolfAxe", 1); // 0 - cF("fWereWolfRunMult", 1.5); // 1.3 - cF("fWereWolfMagicka", 100); // same - cF("fWereWolfAgility", 150); // same - cF("fWereWolfBluntWeapon", 1); // 0 - cF("fWereWolfSecurity", 1); // 0 - cF("fWereWolfPersonality", 1); // 0 - cF("fWereWolfMerchantile", 1); // 0 - cF("fWereWolfHeavyArmor", 1); // 0 - cF("fWereWolfSpear", 1); // 0 - cF("fWereWolfStrength", 150); // same - cF("fWereWolfHealth", 2); // same - cF("fWereWolfMysticism", 1); // 0 - cF("fWereWolfLightArmor", 1); // 0 - cF("fWereWolfWillPower", 1); // 0 - cF("fWereWolfSpeed", 150); // 90 - return false; -} - -void GameSetting::load(ESMReader &esm) -{ - assert(mId != ""); - - mDirty = false; - - // We are apparently allowed to be empty - if (!esm.hasMoreSubs()) + void GameSetting::load (ESMReader &esm) { - mType = VT_None; - return; + mValue.read (esm, ESM::Variant::Format_Gmst); } - // Load some data - esm.getSubName(); - NAME n = esm.retSubName(); - if (n == "STRV") + void GameSetting::save (ESMWriter &esm) { - mStr = esm.getHString(); - mType = VT_String; + mValue.write (esm, ESM::Variant::Format_Gmst); } - else if (n == "INTV") + + int GameSetting::getInt() const { - esm.getHT(mI); - mType = VT_Int; + return mValue.getInteger(); } - else if (n == "FLTV") + + float GameSetting::getFloat() const { - esm.getHT(mF); - mType = VT_Float; + return mValue.getFloat(); } - else - esm.fail("Unwanted subrecord type"); - int spf = esm.getSpecial(); - - // Check if this is one of the mDirty values mentioned above. If it - // is, we set the mDirty flag. This will ONLY work if you've set - // the 'id' string correctly before calling load(). - - if ((spf != SF_Tribunal && isDirtyTribunal()) || (spf != SF_Bloodmoon - && isDirtyBloodmoon())) - mDirty = true; -} -void GameSetting::save(ESMWriter &esm) -{ - switch(mType) + std::string GameSetting::getString() const { - case VT_String: esm.writeHNString("STRV", mStr); break; - case VT_Int: esm.writeHNT("INTV", mI); break; - case VT_Float: esm.writeHNT("FLTV", mF); break; - default: break; + return mValue.getString(); + } + + void GameSetting::blank() + { + mValue.setType (ESM::VT_None); + } + + bool operator== (const GameSetting& left, const GameSetting& right) + { + return left.mValue==right.mValue; } } - -int GameSetting::getInt() const -{ - switch (mType) - { - case VT_Float: return static_cast (mF); - case VT_Int: return mI; - default: throw std::runtime_error ("GMST " + mId + " is not of a numeric type"); - } -} - -float GameSetting::getFloat() const -{ - switch (mType) - { - case VT_Float: return mF; - case VT_Int: return mI; - default: throw std::runtime_error ("GMST " + mId + " is not of a numeric type"); - } -} - -std::string GameSetting::getString() const -{ - if (mType==VT_String) - return mStr; - - throw std::runtime_error ("GMST " + mId + " is not a string"); -} - -} diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index a3471598c..a6e0c2ecb 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -3,7 +3,7 @@ #include -#include "defs.hpp" +#include "variant.hpp" namespace ESM { @@ -19,84 +19,28 @@ class ESMWriter; struct GameSetting { std::string mId; - // One of these is used depending on the variable type - std::string mStr; - int mI; - float mF; - VarType mType; - // Set to true if this is a 'dirty' entry which should be ignored - bool mDirty; - - /* - These functions check if this game setting is one of the "dirty" - GMST records found in many mods. These are due to a serious bug in - the official TES3 editor. It only occurs in the newer editor - versions that came with Tribunal and Bloodmoon, and only if a - modder tries to make a mod without loading the corresponding - expansion master file. For example, if you have Tribunal installed - and try to make a mod without loading Tribunal.esm, the editor - will insert these GMST records as a replacement for the entries it - cannot find in the ESMs. - - The values of these "dirty" records differ in general from their - values as defined in Tribunal.esm and Bloodmoon.esm, and are - always set to the same "default" values. Most of these values are - nonsensical, ie. changing the "Seller Max" string to "Max Sale", - or change the stats of werewolves to useless values like 1. Some - of them break certain spell effects. - - It is most likely that these values are just leftover values from - an early stage of development that are inserted as default values - by the editor code. They are supposed to be overridden when the - correct esm file is loaded. When it isn't loaded however, you get - stuck with the initial value, and this gets written to every mod - by the editor, for some reason. - - Bethesda themselves have fallen for this bug. If you install both - Tribunal and Bloodmoon, the updated Tribunal.esm will contain the - dirty GMST settings from Bloodmoon, and Bloodmoon.esm will contain - some of the dirty settings from Tribunal. In other words, this bug - affects the game EVEN IF YOU DO NOT USE ANY MODS! - - The guys at Bethesda are well aware of this bug (and many others), - as the mod community and fan base complained about them for a long - time. But unfortunately it was never fixed. - - There are several tools available to help modders remove these - records from their files, but not all modders use them, and they - really shouldn't have to. In this file we choose instead to reject - all the corrupt values at load time. - - These functions checks if the current game setting is one of the - "dirty" ones as described above. TODO: I have not checked this - against other sources yet, do that later. Currently recognizes 22 - values for tribunal and 50 for bloodmoon. Legitimate GMSTs in mods - (setting values other than the default "dirty" ones) are not - affected and will work correctly. - */ - - /* - Checks for dirty tribunal values. These will be ignored if found - in any file except when they are found in "Tribunal.esm". - */ - bool isDirtyTribunal(); - - // Bloodmoon variant - bool isDirtyBloodmoon(); + Variant mValue; void load(ESMReader &esm); - + + /// \todo remove the get* functions (redundant, since mValue as equivalent functions now). + int getInt() const; ///< Throws an exception if GMST is not of type int or float. - + float getFloat() const; ///< Throws an exception if GMST is not of type int or float. - + std::string getString() const; ///< Throwns an exception if GMST is not of type string. void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID). }; + + bool operator== (const GameSetting& left, const GameSetting& right); } #endif diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index f237cf780..90f8fcf35 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -84,22 +84,8 @@ void DialInfo::load(ESMReader &esm) SelectStruct ss; ss.mSelectRule = esm.getHString(); - esm.isEmptyOrGetName(); - if (subName.val == REC_INTV) - { - ss.mType = VT_Int; - esm.getHT(ss.mI); - } - else if (subName.val == REC_FLTV) - { - ss.mType = VT_Float; - esm.getHT(ss.mF); - } - else - esm.fail( - "INFO.SCVR must precede INTV or FLTV, not " - + subName.toString()); + ss.mValue.read (esm, Variant::Format_Info); mSelects.push_back(ss); @@ -152,16 +138,11 @@ void DialInfo::save(ESMWriter &esm) for (std::vector::iterator it = mSelects.begin(); it != mSelects.end(); ++it) { esm.writeHNString("SCVR", it->mSelectRule); - switch(it->mType) - { - case VT_Int: esm.writeHNT("INTV", it->mI); break; - case VT_Float: esm.writeHNT("FLTV", it->mF); break; - default: break; - } + it->mValue.write (esm, Variant::Format_Info); } esm.writeHNOString("BNAM", mResultScript); - + switch(mQuestStatus) { case QS_Name: esm.writeHNT("QSTN",'\1'); break; diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index f1decb9c6..2361ed9eb 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -5,6 +5,7 @@ #include #include "defs.hpp" +#include "variant.hpp" namespace ESM { @@ -12,8 +13,6 @@ namespace ESM class ESMReader; class ESMWriter; -// NOT DONE - /* * Dialogue information. A series of these follow after DIAL records, * and form a linked list of dialogue items. @@ -43,9 +42,7 @@ struct DialInfo struct SelectStruct { std::string mSelectRule; // This has a complicated format - float mF; // Only one of 'f' or 'i' is used - int mI; - VarType mType; + Variant mValue; }; // Journal quest indices (introduced with the quest system in Tribunal) @@ -93,8 +90,7 @@ struct DialInfo REC_SNAM = 0x4d414e53, REC_NAME = 0x454d414e, REC_SCVR = 0x52564353, - REC_INTV = 0x56544e49, - REC_FLTV = 0x56544c46, + REC_BNAM = 0x4d414e42, REC_QSTN = 0x4e545351, REC_QSTF = 0x46545351, @@ -106,42 +102,5 @@ struct DialInfo void save(ESMWriter &esm); }; -/* - Some old and unused D code and comments, that might be useful later: - -------- - - // We only need to put each item in ONE list. For if your NPC - // matches this response, then it must match ALL criteria, thus it - // will have to look up itself in all the lists. I think the order - // is well optimized in making the lists as small as possible. - if(this.actor.index != -1) actorDial[this.actor][parent]++; - else if(cell != "") cellDial[cell][parent]++; - else if(this.Class != -1) classDial[this.Class][parent]++; - else if(this.npcFaction != -1) - factionDial[this.npcFaction][parent]++; - else if(this.race != -1) raceDial[this.race][parent]++; - else allDial[parent]++; // Lists dialogues that might - // apply to all npcs. - */ - -// List of dialogue topics (and greetings, voices, etc.) that -// reference other objects. Eg. raceDial is indexed by the indices of -// all races referenced. The value of raceDial is a new AA, which is -// basically used as a map (the int value is just a count and isn't -// used for anything important.) The indices (or elements of the map) -// are the dialogues that reference the given race. I use an AA -// instead of a list or array, since each dialogue can be added lots -// of times. - -/* -int allDial[Dialogue*]; -int classDial[int][Dialogue*]; -int factionDial[int][Dialogue*]; -int actorDial[Item][Dialogue*]; -// If I look up cells on cell load, I don't have to resolve these -// names into anything! -int cellDial[char[]][Dialogue*]; -int raceDial[int][Dialogue*]; -*/ } #endif diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index e1af0ee7c..89b48c27d 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -81,6 +81,7 @@ Land::~Land() void Land::load(ESMReader &esm) { mEsm = &esm; + mPlugin = mEsm->getIndex(); // Get the grid location esm.getSubNameIs("INTV"); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 5bc439c96..c1cce5e7e 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -23,6 +23,7 @@ struct Land int mFlags; // Only first four bits seem to be used, don't know what // they mean. int mX, mY; // Map coordinates. + int mPlugin; // Plugin index, used to reference the correct material palette. // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp new file mode 100644 index 000000000..d25072e54 --- /dev/null +++ b/components/esm/variant.cpp @@ -0,0 +1,282 @@ +#include "variant.hpp" + +#include +#include + +#include "esmreader.hpp" +#include "variantimp.hpp" + +ESM::Variant::Variant() : mType (VT_None), mData (0) {} + +ESM::Variant::~Variant() +{ + delete mData; +} + +ESM::Variant& ESM::Variant::operator= (const Variant& variant) +{ + if (&variant!=this) + { + VariantDataBase *newData = variant.mData ? variant.mData->clone() : 0; + + delete mData; + + mType = variant.mType; + mData = newData; + } + + return *this; +} + +ESM::Variant::Variant (const Variant& variant) +: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : 0) +{} + +ESM::VarType ESM::Variant::getType() const +{ + return mType; +} + +std::string ESM::Variant::getString() const +{ + if (!mData) + throw std::runtime_error ("can not convert empty variant to string"); + + return mData->getString(); +} + +int ESM::Variant::getInteger() const +{ + if (!mData) + throw std::runtime_error ("can not convert empty variant to integer"); + + return mData->getInteger(); +} + +float ESM::Variant::getFloat() const +{ + if (!mData) + throw std::runtime_error ("can not convert empty variant to float"); + + return mData->getFloat(); +} + +void ESM::Variant::read (ESMReader& esm, Format format) +{ + // type + VarType type = VT_Unknown; + + if (format==Format_Global) + { + std::string typeId = esm.getHNString ("FNAM"); + + if (typeId == "s") + type = VT_Short; + else if (typeId == "l") + type = VT_Long; + else if (typeId == "f") + type = VT_Float; + else + esm.fail ("illegal global variable type " + typeId); + } + else if (format==Format_Gmst) + { + if (!esm.hasMoreSubs()) + { + type = VT_None; + } + else + { + esm.getSubName(); + NAME name = esm.retSubName(); + + if (name=="STRV") + { + type = VT_String; + } + else if (name=="INTV") + { + type = VT_Int; + } + else if (name=="FLTV") + { + type = VT_Float; + } + else + esm.fail ("invalid subrecord: " + name.toString()); + } + } + else // info + { + esm.getSubName(); + NAME name = esm.retSubName(); + + if (name=="INTV") + { + type = VT_Int; + } + else if (name=="FLTV") + { + type = VT_Float; + } + else + esm.fail ("invalid subrecord: " + name.toString()); + } + + setType (type); + + // data + if (mData) + mData->read (esm, format, mType); +} + +void ESM::Variant::write (ESMWriter& esm, Format format) const +{ + if (mType==VT_Unknown) + { + throw std::runtime_error ("can not serialise variant of unknown type"); + } + else if (mType==VT_None) + { + if (format==Format_Global) + throw std::runtime_error ("can not serialise variant of type none to global format"); + + if (format==Format_Info) + throw std::runtime_error ("can not serialise variant of type none to info format"); + + // nothing to do here for GMST format + } + else + mData->write (esm, format, mType); +} + +void ESM::Variant::write (std::ostream& stream) const +{ + switch (mType) + { + case VT_Unknown: + + stream << "variant unknown"; + break; + + case VT_None: + + stream << "variant none"; + break; + + case VT_Short: + + stream << "variant short: " << mData->getInteger(); + break; + + case VT_Int: + + stream << "variant int: " << mData->getInteger(); + break; + + case VT_Long: + + stream << "variant long: " << mData->getInteger(); + break; + + case VT_Float: + + stream << "variant float: " << mData->getFloat(); + break; + + case VT_String: + + stream << "variant string: \"" << mData->getString() << "\2"; + break; + } +} + +void ESM::Variant::setType (VarType type) +{ + if (type!=mType) + { + VariantDataBase *newData = 0; + + switch (type) + { + case VT_Unknown: + case VT_None: + + break; // no data + + case VT_Short: + case VT_Int: + case VT_Long: + + newData = new VariantIntegerData (mData); + break; + + case VT_Float: + + newData = new VariantFloatData (mData); + break; + + case VT_String: + + newData = new VariantStringData (mData); + break; + } + + delete mData; + mData = newData; + mType = type; + } +} + +void ESM::Variant::setString (const std::string& value) +{ + if (!mData) + throw std::runtime_error ("can not assign string to empty variant"); + + mData->setString (value); +} + +void ESM::Variant::setInteger (int value) +{ + if (!mData) + throw std::runtime_error ("can not assign integer to empty variant"); + + mData->setInteger (value); +} + +void ESM::Variant::setFloat (float value) +{ + if (!mData) + throw std::runtime_error ("can not assign float to empty variant"); + + mData->setFloat (value); +} + +bool ESM::Variant::isEqual (const Variant& value) const +{ + if (mType!=value.mType) + return false; + + if (!mData) + return true; + + assert (value.mData); + + return mData->isEqual (*value.mData); +} + +std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) +{ + value.write (stream); + return stream; +} + +bool ESM::operator== (const Variant& left, const Variant& right) +{ + return left.isEqual (right); +} + +bool ESM::operator!= (const Variant& left, const Variant& right) +{ + return !(left==right); +} \ No newline at end of file diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp new file mode 100644 index 000000000..8c5f3b3d4 --- /dev/null +++ b/components/esm/variant.hpp @@ -0,0 +1,86 @@ +#ifndef OPENMW_ESM_VARIANT_H +#define OPENMW_ESM_VARIANT_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + enum VarType + { + VT_Unknown, + VT_None, + VT_Short, // stored as a float, kinda + VT_Int, + VT_Long, // stored as a float + VT_Float, + VT_String + }; + + class VariantDataBase; + + class Variant + { + VarType mType; + VariantDataBase *mData; + + public: + + enum Format + { + Format_Global, + Format_Gmst, + Format_Info + }; + + Variant(); + + ~Variant(); + + Variant& operator= (const Variant& variant); + + Variant (const Variant& variant); + + VarType getType() const; + + std::string getString() const; + ///< Will throw an exception, if value can not be represented as a string. + + int getInteger() const; + ///< Will throw an exception, if value can not be represented as an integer (implicit + /// casting of float values is permitted). + + float getFloat() const; + ///< Will throw an exception, if value can not be represented as a float value. + + void read (ESMReader& esm, Format format); + + void write (ESMWriter& esm, Format format) const; + + void write (std::ostream& stream) const; + ///< Write in text format. + + void setType (VarType type); + + void setString (const std::string& value); + ///< Will throw an exception, if type is not compatible with string. + + void setInteger (int value); + ///< Will throw an exception, if type is not compatible with integer. + + void setFloat (float value); + ///< Will throw an exception, if type is not compatible with float. + + bool isEqual (const Variant& value) const; + }; + + std::ostream& operator<<(std::ostream& stream, const Variant& value); + + bool operator== (const Variant& left, const Variant& right); + bool operator!= (const Variant& left, const Variant& right); +} + +#endif \ No newline at end of file diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp new file mode 100644 index 000000000..160402aa4 --- /dev/null +++ b/components/esm/variantimp.cpp @@ -0,0 +1,280 @@ + +#include "variantimp.hpp" + +#include + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +ESM::VariantDataBase::~VariantDataBase() {} + +std::string ESM::VariantDataBase::getString (bool default_) const +{ + if (default_) + return ""; + + throw std::runtime_error ("can not convert variant to string"); +} + +int ESM::VariantDataBase::getInteger (bool default_) const +{ + if (default_) + return 0; + + throw std::runtime_error ("can not convert variant to integer"); +} + +float ESM::VariantDataBase::getFloat (bool default_) const +{ + if (default_) + return 0; + + throw std::runtime_error ("can not convert variant to float"); +} + +void ESM::VariantDataBase::setString (const std::string& value) +{ + throw std::runtime_error ("conversion of string to variant not possible"); +} + +void ESM::VariantDataBase::setInteger (int value) +{ + throw std::runtime_error ("conversion of integer to variant not possible"); +} + +void ESM::VariantDataBase::setFloat (float value) +{ + throw std::runtime_error ("conversion of float to variant not possible"); +} + + + +ESM::VariantStringData::VariantStringData (const VariantDataBase *data) +{ + if (data) + mValue = data->getString (true); +} + +ESM::VariantDataBase *ESM::VariantStringData::clone() const +{ + return new VariantStringData (*this); +} + +std::string ESM::VariantStringData::getString (bool default_) const +{ + return mValue; +} + +void ESM::VariantStringData::setString (const std::string& value) +{ + mValue = value; +} + +void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarType type) +{ + if (type!=VT_String) + throw std::logic_error ("not a string type"); + + if (format==Variant::Format_Global) + esm.fail ("global variables of type string not supported"); + + if (format==Variant::Format_Info) + esm.fail ("info variables of type string not supported"); + + // GMST + mValue = esm.getHString(); +} + +void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarType type) const +{ + if (type!=VT_String) + throw std::logic_error ("not a string type"); + + if (format==Variant::Format_Global) + throw std::runtime_error ("global variables of type string not supported"); + + if (format==Variant::Format_Info) + throw std::runtime_error ("info variables of type string not supported"); + + // GMST + esm.writeHNString ("STRV", mValue); +} + +bool ESM::VariantStringData::isEqual (const VariantDataBase& value) const +{ + return dynamic_cast (value).mValue==mValue; +} + + + +ESM::VariantIntegerData::VariantIntegerData (const VariantDataBase *data) : mValue (0) +{ + if (data) + mValue = data->getInteger (true); +} + +ESM::VariantDataBase *ESM::VariantIntegerData::clone() const +{ + return new VariantIntegerData (*this); +} + +int ESM::VariantIntegerData::getInteger (bool default_) const +{ + return mValue; +} + +float ESM::VariantIntegerData::getFloat (bool default_) const +{ + return mValue; +} + +void ESM::VariantIntegerData::setInteger (int value) +{ + mValue = value; +} + +void ESM::VariantIntegerData::setFloat (float value) +{ + mValue = static_cast (value); +} + +void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarType type) +{ + if (type!=VT_Short && type!=VT_Long && type!=VT_Int) + throw std::logic_error ("not an integer type"); + + if (format==Variant::Format_Global) + { + float value; + esm.getHNT (value, "FLTV"); + + if (type==VT_Short) + { + if (value!=value) + mValue = 0; // nan + else + mValue = static_cast (value); + } + else if (type==VT_Long) + mValue = static_cast (value); + else + esm.fail ("unsupported global variable integer type"); + } + else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + { + if (type!=VT_Int) + { + std::ostringstream stream; + stream + << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info") + << " variable integer type"; + esm.fail (stream.str()); + } + + esm.getHT (mValue); + } +} + +void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, VarType type) const +{ + if (type!=VT_Short && type!=VT_Long && type!=VT_Int) + throw std::logic_error ("not an integer type"); + + if (format==Variant::Format_Global) + { + if (type==VT_Short || type==VT_Long) + { + float value = mValue; + esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l"); + esm.writeHNT ("FLTV", value); + } + else + throw std::runtime_error ("unsupported global variable integer type"); + } + else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + { + if (type==VT_Int) + { + std::ostringstream stream; + stream + << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info") + << " variable integer type"; + throw std::runtime_error (stream.str()); + } + + esm.writeHNT ("INTV", mValue); + } +} + +bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const +{ + return dynamic_cast (value).mValue==mValue; +} + + +ESM::VariantFloatData::VariantFloatData (const VariantDataBase *data) : mValue (0) +{ + if (data) + mValue = data->getFloat (true); +} + +ESM::VariantDataBase *ESM::VariantFloatData::clone() const +{ + return new VariantFloatData (*this); +} + +int ESM::VariantFloatData::getInteger (bool default_) const +{ + return static_cast (mValue); +} + +float ESM::VariantFloatData::getFloat (bool default_) const +{ + return mValue; +} + +void ESM::VariantFloatData::setInteger (int value) +{ + mValue = value; +} + +void ESM::VariantFloatData::setFloat (float value) +{ + mValue = value; +} + +void ESM::VariantFloatData::read (ESMReader& esm, Variant::Format format, VarType type) +{ + if (type!=VT_Float) + throw std::logic_error ("not a float type"); + + if (format==Variant::Format_Global) + { + esm.getHNT (mValue, "FLTV"); + } + else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + { + esm.getHT (mValue); + } +} + +void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarType type) const +{ + if (type!=VT_Float) + throw std::logic_error ("not a float type"); + + if (format==Variant::Format_Global) + { + esm.writeHNString ("FNAM", "f"); + esm.writeHNT ("FLTV", mValue); + } + else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + { + esm.writeHNT ("FLTV", mValue); + } +} + +bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const +{ + return dynamic_cast (value).mValue==mValue; +} \ No newline at end of file diff --git a/components/esm/variantimp.hpp b/components/esm/variantimp.hpp new file mode 100644 index 000000000..1dc20c21f --- /dev/null +++ b/components/esm/variantimp.hpp @@ -0,0 +1,179 @@ +#ifndef OPENMW_ESM_VARIANTIMP_H +#define OPENMW_ESM_VARIANTIMP_H + +#include + +#include "variant.hpp" + +namespace ESM +{ + class VariantDataBase + { + public: + + virtual ~VariantDataBase(); + + virtual VariantDataBase *clone() const = 0; + + virtual std::string getString (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as a string. + /// + /// \note Numeric values are not converted to strings. + /// + /// \param default_ Return a default value instead of throwing an exception. + /// + /// Default-implementation: throw an exception. + + virtual int getInteger (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as an integer (implicit + /// casting of float values is permitted). + /// + /// \param default_ Return a default value instead of throwing an exception. + /// + /// Default-implementation: throw an exception. + + virtual float getFloat (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as a float value. + /// + /// \param default_ Return a default value instead of throwing an exception. + /// + /// Default-implementation: throw an exception. + + virtual void setString (const std::string& value); + ///< Will throw an exception, if type is not compatible with string. + /// + /// Default-implementation: throw an exception. + + virtual void setInteger (int value); + ///< Will throw an exception, if type is not compatible with integer. + /// + /// Default-implementation: throw an exception. + + virtual void setFloat (float value); + ///< Will throw an exception, if type is not compatible with float. + /// + /// Default-implementation: throw an exception. + + virtual void read (ESMReader& esm, Variant::Format format, VarType type) = 0; + ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + + virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const = 0; + ///< If \a type is not supported by \a format, an exception is thrown. + + virtual bool isEqual (const VariantDataBase& value) const = 0; + ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + + }; + + class VariantStringData : public VariantDataBase + { + std::string mValue; + + public: + + VariantStringData (const VariantDataBase *data = 0); + ///< Calling the constructor with an incompatible data type will result in a silent + /// default initialisation. + + virtual VariantDataBase *clone() const; + + virtual std::string getString (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as a string. + /// + /// \note Numeric values are not converted to strings. + /// + /// \param default_ Return a default value instead of throwing an exception. + + virtual void setString (const std::string& value); + ///< Will throw an exception, if type is not compatible with string. + + virtual void read (ESMReader& esm, Variant::Format format, VarType type); + ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + + virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const; + ///< If \a type is not supported by \a format, an exception is thrown. + + virtual bool isEqual (const VariantDataBase& value) const; + ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + }; + + class VariantIntegerData : public VariantDataBase + { + int mValue; + + public: + + VariantIntegerData (const VariantDataBase *data = 0); + ///< Calling the constructor with an incompatible data type will result in a silent + /// default initialisation. + + virtual VariantDataBase *clone() const; + + virtual int getInteger (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as an integer (implicit + /// casting of float values is permitted). + /// + /// \param default_ Return a default value instead of throwing an exception. + + virtual float getFloat (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as a float value. + /// + /// \param default_ Return a default value instead of throwing an exception. + + virtual void setInteger (int value); + ///< Will throw an exception, if type is not compatible with integer. + + virtual void setFloat (float value); + ///< Will throw an exception, if type is not compatible with float. + + virtual void read (ESMReader& esm, Variant::Format format, VarType type); + ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + + virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const; + ///< If \a type is not supported by \a format, an exception is thrown. + + virtual bool isEqual (const VariantDataBase& value) const; + ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + }; + + class VariantFloatData : public VariantDataBase + { + float mValue; + + public: + + VariantFloatData (const VariantDataBase *data = 0); + ///< Calling the constructor with an incompatible data type will result in a silent + /// default initialisation. + + virtual VariantDataBase *clone() const; + + virtual int getInteger (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as an integer (implicit + /// casting of float values is permitted). + /// + /// \param default_ Return a default value instead of throwing an exception. + + virtual float getFloat (bool default_ = false) const; + ///< Will throw an exception, if value can not be represented as a float value. + /// + /// \param default_ Return a default value instead of throwing an exception. + + virtual void setInteger (int value); + ///< Will throw an exception, if type is not compatible with integer. + + virtual void setFloat (float value); + ///< Will throw an exception, if type is not compatible with float. + + virtual void read (ESMReader& esm, Variant::Format format, VarType type); + ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + + virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const; + ///< If \a type is not supported by \a format, an exception is thrown. + + virtual bool isEqual (const VariantDataBase& value) const; + ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + }; +} + +#endif diff --git a/apps/launcher/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp similarity index 70% rename from apps/launcher/model/datafilesmodel.cpp rename to components/fileorderlist/model/datafilesmodel.cpp index d85a15e73..b33e2e12a 100644 --- a/apps/launcher/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -8,8 +8,6 @@ #include "esm/esmfile.hpp" -#include "../utils/naturalsort.hpp" - #include "datafilesmodel.hpp" DataFilesModel::DataFilesModel(QObject *parent) : @@ -159,7 +157,7 @@ Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const if (!file) return Qt::NoItemFlags; - if (mAvailableFiles.contains(file->fileName())) { + if (canBeChecked(file)) { if (index.column() == 0) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { @@ -206,53 +204,55 @@ bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, in return false; if (role == Qt::CheckStateRole) { - - emit layoutAboutToBeChanged(); - QString name = item(index.row())->fileName(); mCheckStates[name] = static_cast(value.toInt()); - emit checkedItemsChanged(checkedItems(), uncheckedItems()); - emit layoutChanged(); + // Force a redraw of the view since unchecking one item can affect another + QModelIndex firstIndex = indexFromItem(mFiles.first()); + QModelIndex lastIndex = indexFromItem(mFiles.last()); + + emit dataChanged(firstIndex, lastIndex); + emit checkedItemsChanged(checkedItems()); return true; } return false; } +bool lessThanEsmFile(const EsmFile *e1, const EsmFile *e2) +{ + //Masters first then alphabetically + if (e1->fileName().endsWith(".esm") && !e2->fileName().endsWith(".esm")) + return true; + if (!e1->fileName().endsWith(".esm") && e2->fileName().endsWith(".esm")) + return false; + + return e1->fileName().toLower() < e2->fileName().toLower(); +} + +bool lessThanDate(const EsmFile *e1, const EsmFile *e2) +{ + if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) { + return true; + } else { + return false; + } +// if (!e1->fileName().endsWith(".esm") && e2->fileName().endsWith(".esm")) +// return false; + +// return e1->fileName().toLower() < e2->fileName().toLower(); +} + void DataFilesModel::sort(int column, Qt::SortOrder order) { - // TODO: Make this more efficient emit layoutAboutToBeChanged(); - QList sortedFiles; - - QMultiMap timestamps; - - foreach (EsmFile *file, mFiles) - timestamps.insert(file->modified().toString(Qt::ISODate), file->fileName()); - - QMapIterator ti(timestamps); - - while (ti.hasNext()) { - ti.next(); - - QModelIndex index = indexFromItem(findItem(ti.value())); - - if (!index.isValid()) - continue; - - EsmFile *file = item(index.row()); - - if (!file) - continue; - - sortedFiles.append(file); + if (column == 3) { + qSort(mFiles.begin(), mFiles.end(), lessThanDate); + } else { + qSort(mFiles.begin(), mFiles.end(), lessThanEsmFile); } - mFiles.clear(); - mFiles = sortedFiles; - emit layoutChanged(); } @@ -263,79 +263,22 @@ void DataFilesModel::addFile(EsmFile *file) emit endInsertRows(); } -void DataFilesModel::addMasters(const QString &path) +void DataFilesModel::addFiles(const QString &path) { QDir dir(path); - dir.setNameFilters(QStringList(QLatin1String("*.esp"))); - - // Read the dependencies from the plugins - foreach (const QString &path, dir.entryList()) { - try { - ESM::ESMReader fileReader; - fileReader.setEncoding(mEncoding.toStdString()); - fileReader.open(dir.absoluteFilePath(path).toStdString()); - - ESM::ESMReader::MasterList mlist = fileReader.getMasters(); - - for (unsigned int i = 0; i < mlist.size(); ++i) { - QString master = QString::fromStdString(mlist[i].name); - - // Add the plugin to the internal dependency map - mDependencies[master].append(path); - - // Don't add esps - if (master.endsWith(".esp", Qt::CaseInsensitive)) - continue; - - QFileInfo info(dir.absoluteFilePath(master)); - - EsmFile *file = new EsmFile(master); - file->setDates(info.lastModified(), info.lastRead()); - - // Add the master to the table - if (findItem(master) == 0) - addFile(file); - - - } - - } catch(std::runtime_error &e) { - // An error occurred while reading the .esp - qWarning() << "Error reading esp: " << e.what(); - continue; - } - } - - // See if the masters actually exist in the filesystem - dir.setNameFilters(QStringList(QLatin1String("*.esm"))); - - foreach (const QString &path, dir.entryList()) { - QFileInfo info(dir.absoluteFilePath(path)); - - if (findItem(path) == 0) { - EsmFile *file = new EsmFile(path); - file->setDates(info.lastModified(), info.lastRead()); - - addFile(file); - } - - // Make the master selectable - mAvailableFiles.append(path); - } -} - -void DataFilesModel::addPlugins(const QString &path) -{ - QDir dir(path); - dir.setNameFilters(QStringList(QLatin1String("*.esp"))); + QStringList filters; + filters << "*.esp" << "*.esm"; + dir.setNameFilters(filters); foreach (const QString &path, dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path)); EsmFile *file = new EsmFile(path); + try { ESM::ESMReader fileReader; - fileReader.setEncoding(mEncoding.toStdString()); + ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(mEncoding.toStdString())); + fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); ESM::ESMReader::MasterList mlist = fileReader.getMasters(); @@ -344,9 +287,6 @@ void DataFilesModel::addPlugins(const QString &path) for (unsigned int i = 0; i < mlist.size(); ++i) { QString master = QString::fromStdString(mlist[i].name); masters.append(master); - - // Add the plugin to the internal dependency map - mDependencies[master].append(path); } file->setAuthor(QString::fromStdString(fileReader.getAuthor())); @@ -359,7 +299,8 @@ void DataFilesModel::addPlugins(const QString &path) // Put the file in the table - addFile(file); + if (findItem(path) == 0) + addFile(file); } catch(std::runtime_error &e) { // An error occurred while reading the .esp qWarning() << "Error reading esp: " << e.what(); @@ -418,13 +359,32 @@ QStringList DataFilesModel::checkedItems() QString name = file->fileName(); // Only add the items that are in the checked list and available - if (mCheckStates[name] == Qt::Checked && mAvailableFiles.contains(name)) + if (mCheckStates[name] == Qt::Checked && canBeChecked(file)) list << name; } return list; } +QStringList DataFilesModel::checkedItemsPaths() +{ + QStringList list; + + QList::ConstIterator it; + QList::ConstIterator itEnd = mFiles.constEnd(); + + int i = 0; + for (it = mFiles.constBegin(); it != itEnd; ++it) { + EsmFile *file = item(i); + ++i; + + if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) + list << file->path(); + } + + return list; +} + void DataFilesModel::uncheckAll() { emit layoutAboutToBeChanged(); @@ -453,24 +413,17 @@ QStringList DataFilesModel::uncheckedItems() return list; } -void DataFilesModel::slotcheckedItemsChanged(const QStringList &checkedItems, const QStringList &unCheckedItems) +bool DataFilesModel::canBeChecked(EsmFile *file) const { - emit layoutAboutToBeChanged(); - - QStringList list; - - foreach (const QString &file, checkedItems) { - list << mDependencies[file]; - } - - foreach (const QString &file, unCheckedItems) { - foreach (const QString &remove, mDependencies[file]) { - list.removeAll(remove); + //element can be checked if all its dependencies are + bool canBeChecked = true; + foreach (const QString &master, file->masters()) + { + if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked) + { + canBeChecked = false; + break; } } - - mAvailableFiles.clear(); - mAvailableFiles.append(list); - - emit layoutChanged(); + return canBeChecked; } diff --git a/apps/launcher/model/datafilesmodel.hpp b/components/fileorderlist/model/datafilesmodel.hpp similarity index 82% rename from apps/launcher/model/datafilesmodel.hpp rename to components/fileorderlist/model/datafilesmodel.hpp index 29a770a86..0a07a536f 100644 --- a/apps/launcher/model/datafilesmodel.hpp +++ b/components/fileorderlist/model/datafilesmodel.hpp @@ -34,15 +34,13 @@ public: void setEncoding(const QString &encoding); - void addFile(EsmFile *file); - - void addMasters(const QString &path); - void addPlugins(const QString &path); + void addFiles(const QString &path); void uncheckAll(); QStringList checkedItems(); QStringList uncheckedItems(); + QStringList checkedItemsPaths(); Qt::CheckState checkState(const QModelIndex &index); void setCheckState(const QModelIndex &index, Qt::CheckState state); @@ -52,16 +50,13 @@ public: EsmFile* item(int row) const; signals: - void checkedItemsChanged(const QStringList checkedItems, const QStringList unCheckedItems); - -public slots: - void slotcheckedItemsChanged(const QStringList &checkedItems, const QStringList &unCheckedItems); + void checkedItemsChanged(const QStringList &items); private: + bool canBeChecked(EsmFile *file) const; + void addFile(EsmFile *file); + QList mFiles; - QStringList mAvailableFiles; - - QHash mDependencies; QHash mCheckStates; QString mEncoding; diff --git a/apps/launcher/model/esm/esmfile.cpp b/components/fileorderlist/model/esm/esmfile.cpp similarity index 100% rename from apps/launcher/model/esm/esmfile.cpp rename to components/fileorderlist/model/esm/esmfile.cpp diff --git a/apps/launcher/model/esm/esmfile.hpp b/components/fileorderlist/model/esm/esmfile.hpp similarity index 64% rename from apps/launcher/model/esm/esmfile.hpp rename to components/fileorderlist/model/esm/esmfile.hpp index ad267aa75..52b3fbd00 100644 --- a/apps/launcher/model/esm/esmfile.hpp +++ b/components/fileorderlist/model/esm/esmfile.hpp @@ -26,15 +26,15 @@ public: void setMasters(const QStringList &masters); void setDescription(const QString &description); - inline QString fileName() { return mFileName; } - inline QString author() { return mAuthor; } - inline int size() { return mSize; } - inline QDateTime modified() { return mModified; } - inline QDateTime accessed() { return mAccessed; } - inline float version() { return mVersion; } - inline QString path() { return mPath; } - inline QStringList masters() { return mMasters; } - inline QString description() { return mDescription; } + inline QString fileName() const { return mFileName; } + inline QString author() const { return mAuthor; } + inline int size() const { return mSize; } + inline QDateTime modified() const { return mModified; } + inline QDateTime accessed() const { return mAccessed; } + inline float version() const { return mVersion; } + inline QString path() const { return mPath; } + inline QStringList masters() const { return mMasters; } + inline QString description() const { return mDescription; } private: diff --git a/apps/launcher/model/modelitem.cpp b/components/fileorderlist/model/modelitem.cpp similarity index 100% rename from apps/launcher/model/modelitem.cpp rename to components/fileorderlist/model/modelitem.cpp diff --git a/apps/launcher/model/modelitem.hpp b/components/fileorderlist/model/modelitem.hpp similarity index 100% rename from apps/launcher/model/modelitem.hpp rename to components/fileorderlist/model/modelitem.hpp diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp new file mode 100644 index 000000000..6be152b55 --- /dev/null +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -0,0 +1,17 @@ +#include "pluginsproxymodel.hpp" + +PluginsProxyModel::PluginsProxyModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ +} + +PluginsProxyModel::~PluginsProxyModel() +{ +} + +QVariant PluginsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Vertical || role != Qt::DisplayRole) + return QSortFilterProxyModel::headerData(section, orientation, role); + return section + 1; +} diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp new file mode 100644 index 000000000..8fde73236 --- /dev/null +++ b/components/fileorderlist/model/pluginsproxymodel.hpp @@ -0,0 +1,18 @@ +#ifndef PLUGINSPROXYMODEL_HPP +#define PLUGINSPROXYMODEL_HPP + +#include + +class QVariant; + +class PluginsProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit PluginsProxyModel(QObject *parent = 0); + ~PluginsProxyModel(); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; +}; + +#endif // PLUGINSPROXYMODEL_HPP diff --git a/components/fileorderlist/utils/comboboxlineedit.cpp b/components/fileorderlist/utils/comboboxlineedit.cpp new file mode 100644 index 000000000..4d62e1399 --- /dev/null +++ b/components/fileorderlist/utils/comboboxlineedit.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include "comboboxlineedit.hpp" + +ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) + : QLineEdit(parent) +{ + mClearButton = new QToolButton(this); + QPixmap pixmap(":images/clear.png"); + mClearButton->setIcon(QIcon(pixmap)); + mClearButton->setIconSize(pixmap.size()); + mClearButton->setCursor(Qt::ArrowCursor); + mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + mClearButton->hide(); + connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); + connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + + setObjectName(QString("ComboBoxLineEdit")); + setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); +} + +void ComboBoxLineEdit::resizeEvent(QResizeEvent *) +{ + QSize sz = mClearButton->sizeHint(); + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + mClearButton->move(rect().right() - frameWidth - sz.width(), + (rect().bottom() + 1 - sz.height())/2); +} + +void ComboBoxLineEdit::updateClearButton(const QString& text) +{ + mClearButton->setVisible(!text.isEmpty()); +} diff --git a/components/fileorderlist/utils/comboboxlineedit.hpp b/components/fileorderlist/utils/comboboxlineedit.hpp new file mode 100644 index 000000000..ba10731ae --- /dev/null +++ b/components/fileorderlist/utils/comboboxlineedit.hpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (c) 2007 Trolltech ASA +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#ifndef LINEEDIT_H +#define LINEEDIT_H + +#include + +class QToolButton; + +class ComboBoxLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + ComboBoxLineEdit(QWidget *parent = 0); + +protected: + void resizeEvent(QResizeEvent *); + +private slots: + void updateClearButton(const QString &text); + +private: + QToolButton *mClearButton; +}; + +#endif // LIENEDIT_H + diff --git a/apps/launcher/utils/lineedit.cpp b/components/fileorderlist/utils/lineedit.cpp similarity index 82% rename from apps/launcher/utils/lineedit.cpp rename to components/fileorderlist/utils/lineedit.cpp index dac196425..b0f339589 100644 --- a/apps/launcher/utils/lineedit.cpp +++ b/components/fileorderlist/utils/lineedit.cpp @@ -1,7 +1,8 @@ -#include "lineedit.hpp" #include #include +#include "lineedit.hpp" + LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { @@ -13,9 +14,11 @@ LineEdit::LineEdit(QWidget *parent) mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); mClearButton->hide(); connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); - connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateCloseButton(const QString&))); + connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - setStyleSheet(QString("QLineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); + + setObjectName(QString("LineEdit")); + setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); QSize msz = minimumSizeHint(); setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2), qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); @@ -29,9 +32,7 @@ void LineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void LineEdit::updateCloseButton(const QString& text) +void LineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } - - diff --git a/apps/launcher/utils/lineedit.hpp b/components/fileorderlist/utils/lineedit.hpp similarity index 92% rename from apps/launcher/utils/lineedit.hpp rename to components/fileorderlist/utils/lineedit.hpp index 2ed76d6eb..14bd7b1b4 100644 --- a/apps/launcher/utils/lineedit.hpp +++ b/components/fileorderlist/utils/lineedit.hpp @@ -25,7 +25,7 @@ protected: void resizeEvent(QResizeEvent *); private slots: - void updateCloseButton(const QString &text); + void updateClearButton(const QString &text); private: QToolButton *mClearButton; diff --git a/apps/launcher/utils/naturalsort.cpp b/components/fileorderlist/utils/naturalsort.cpp similarity index 100% rename from apps/launcher/utils/naturalsort.cpp rename to components/fileorderlist/utils/naturalsort.cpp diff --git a/apps/launcher/utils/naturalsort.hpp b/components/fileorderlist/utils/naturalsort.hpp similarity index 100% rename from apps/launcher/utils/naturalsort.hpp rename to components/fileorderlist/utils/naturalsort.hpp diff --git a/components/fileorderlist/utils/profilescombobox.cpp b/components/fileorderlist/utils/profilescombobox.cpp new file mode 100644 index 000000000..c3ff953ae --- /dev/null +++ b/components/fileorderlist/utils/profilescombobox.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include + +#include "profilescombobox.hpp" +#include "comboboxlineedit.hpp" + +ProfilesComboBox::ProfilesComboBox(QWidget *parent) : + QComboBox(parent) +{ + mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore + setEditEnabled(true); + setValidator(mValidator); + setCompleter(0); + + connect(this, SIGNAL(currentIndexChanged(int)), this, + SLOT(slotIndexChanged(int))); + + setInsertPolicy(QComboBox::NoInsert); +} + +void ProfilesComboBox::setEditEnabled(bool editable) +{ + if (isEditable() == editable) + return; + + if (!editable) { + disconnect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished())); + disconnect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); + return setEditable(false); + } + + // Reset the completer and validator + setEditable(true); + setValidator(mValidator); + + ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); + setLineEdit(edit); + setCompleter(0); + + connect(lineEdit(), SIGNAL(editingFinished()), this, + SLOT(slotEditingFinished())); + + connect(lineEdit(), SIGNAL(textChanged(QString)), this, + SLOT(slotTextChanged(QString))); +} + +void ProfilesComboBox::slotTextChanged(const QString &text) +{ + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text,Qt::red); + + int index = findText(text); + + if (text.isEmpty() || (index != -1 && index != currentIndex())) { + lineEdit()->setPalette(*palette); + } else { + lineEdit()->setPalette(QApplication::palette()); + } +} + +void ProfilesComboBox::slotEditingFinished() +{ + QString current = currentText(); + QString previous = itemText(currentIndex()); + + if (currentIndex() == -1) + return; + + if (current.isEmpty()) + return; + + if (current == previous) + return; + + if (findText(current) != -1) + return; + + setItemText(currentIndex(), current); + emit(profileRenamed(previous, current)); +} + +void ProfilesComboBox::slotIndexChanged(int index) +{ + if (index == -1) + return; + + emit(profileChanged(mOldProfile, currentText())); + mOldProfile = itemText(index); +} diff --git a/apps/launcher/utils/profilescombobox.hpp b/components/fileorderlist/utils/profilescombobox.hpp similarity index 88% rename from apps/launcher/utils/profilescombobox.hpp rename to components/fileorderlist/utils/profilescombobox.hpp index c7da60d2a..08ead9a7a 100644 --- a/apps/launcher/utils/profilescombobox.hpp +++ b/components/fileorderlist/utils/profilescombobox.hpp @@ -4,7 +4,6 @@ #include class QString; - class QRegExpValidator; class ProfilesComboBox : public QComboBox @@ -19,8 +18,9 @@ signals: void profileRenamed(const QString &oldName, const QString &newName); private slots: - void slotReturnPressed(); + void slotEditingFinished(); void slotIndexChanged(int index); + void slotTextChanged(const QString &text); private: QString mOldProfile; diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp new file mode 100644 index 000000000..321bcf7c8 --- /dev/null +++ b/components/files/constrainedfiledatastream.cpp @@ -0,0 +1,172 @@ +#include "constrainedfiledatastream.hpp" +#include "lowlevelfile.hpp" + +#include +#include + +#include + +namespace { + +class ConstrainedDataStream : public Ogre::DataStream { +public: + + static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any + static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call + + ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) + { + mFile.open (fname.c_str ()); + mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; + + mPos = 0; + mOrigin = start; + mExtent = start + mSize; + + mBufferOrigin = 0; + mBufferExtent = 0; + } + + + size_t read(void* buf, size_t count) + { + assert (mPos <= mSize); + + uint8_t * out = reinterpret_cast (buf); + + size_t posBeg = mOrigin + mPos; + size_t posEnd = posBeg + count; + + if (posEnd > mExtent) + posEnd = mExtent; + + size_t posCur = posBeg; + + while (posCur != posEnd) + { + size_t readLeft = posEnd - posCur; + + if (posCur < mBufferOrigin || posCur >= mBufferExtent) + { + if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent)) + { + assert (mFile.tell () == mBufferExtent); + + if (posCur != mBufferExtent) + mFile.seek (posCur); + + posCur += mFile.read (out, readLeft); + + mBufferOrigin = mBufferExtent = posCur; + + mPos = posCur - mOrigin; + + return posCur - posBeg; + } + else + { + size_t newBufferOrigin; + + if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize)) + newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0); + else + newBufferOrigin = posCur; + + fill (newBufferOrigin); + } + } + + size_t xfer = std::min (readLeft, mBufferExtent - posCur); + + memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer); + + posCur += xfer; + out += xfer; + } + + count = posEnd - posBeg; + mPos += count; + return count; + } + + void skip(long count) + { + assert (mPos <= mSize); + + if((count >= 0 && (size_t)count <= mSize-mPos) || + (count < 0 && (size_t)-count <= mPos)) + mPos += count; + } + + void seek(size_t pos) + { + assert (mPos <= mSize); + + if (pos < mSize) + mPos = pos; + } + + virtual size_t tell() const + { + assert (mPos <= mSize); + + return mPos; + } + + virtual bool eof() const + { + assert (mPos <= mSize); + + return mPos == mSize; + } + + virtual void close() + { + mFile.close(); + } + +private: + + void fill (size_t newOrigin) + { + assert (mFile.tell () == mBufferExtent); + + size_t newExtent = newOrigin + sBufferSize; + + if (newExtent > mExtent) + newExtent = mExtent; + + size_t oldExtent = mBufferExtent; + + if (newOrigin != oldExtent) + mFile.seek (newOrigin); + + mBufferOrigin = mBufferExtent = newOrigin; + + size_t amountRequested = newExtent - newOrigin; + + size_t amountRead = mFile.read (mBuffer, amountRequested); + + if (amountRead != amountRequested) + throw std::runtime_error ("An unexpected condition occurred while reading from a file."); + + mBufferExtent = newExtent; + } + + LowLevelFile mFile; + + size_t mOrigin; + size_t mExtent; + size_t mPos; + + uint8_t mBuffer [sBufferSize]; + size_t mBufferOrigin; + size_t mBufferExtent; +}; + +} // end of unnamed namespace + +Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length) +{ + return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length)); +} diff --git a/components/files/constrainedfiledatastream.hpp b/components/files/constrainedfiledatastream.hpp new file mode 100644 index 000000000..367defcbc --- /dev/null +++ b/components/files/constrainedfiledatastream.hpp @@ -0,0 +1,8 @@ +#ifndef COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP +#define COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP + +#include + +Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset = 0, size_t length = 0xFFFFFFFF); + +#endif // COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp index ce67f0c66..ce2d95f57 100644 --- a/components/files/filelibrary.cpp +++ b/components/files/filelibrary.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include <../components/misc/stringops.hpp> namespace Files { @@ -45,14 +45,14 @@ namespace Files if( !acceptableExtensions.empty() ) { fileExtension = boost::filesystem::path (listIter->extension()).string(); - boost::algorithm::to_lower(fileExtension); + Misc::StringUtils::toLower(fileExtension); if(!containsVectorString(acceptableExtensions, fileExtension)) continue; } type = boost::filesystem::path (listIter->parent_path().leaf()).string(); if (!strict) - boost::algorithm::to_lower(type); + Misc::StringUtils::toLower(type); mMap[type].push_back(*listIter); // std::cout << "Added path: " << listIter->string() << " in section "<< type < #include +#include <../components/misc/stringops.hpp> namespace Files { @@ -87,7 +88,7 @@ bool isFile(const char *name) if (!strict) { - boost::algorithm::to_lower(toFindStr); + Misc::StringUtils::toLower(toFindStr); } for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) @@ -99,7 +100,7 @@ bool isFile(const char *name) if (!strict) { - boost::algorithm::to_lower(fullPath); + Misc::StringUtils::toLower(fullPath); } if(endingMatches(fullPath, toFindStr)) { diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp index dce4f96c2..a309dc9fb 100644 --- a/components/files/fixedpath.hpp +++ b/components/files/fixedpath.hpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/fixedpath.hpp */ - #ifndef COMPONENTS_FILES_FIXEDPATH_HPP #define COMPONENTS_FILES_FIXEDPATH_HPP diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index 0f08b67fe..c974a91d3 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/linuxpath.cpp */ - #include "linuxpath.hpp" #if defined(__linux__) || defined(__FreeBSD__) diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp index 09acd2be7..6acf2a2d5 100644 --- a/components/files/linuxpath.hpp +++ b/components/files/linuxpath.hpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/linuxpath.hpp */ - #ifndef COMPONENTS_FILES_LINUXPATH_H #define COMPONENTS_FILES_LINUXPATH_H diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp new file mode 100644 index 000000000..71fd1b523 --- /dev/null +++ b/components/files/lowlevelfile.cpp @@ -0,0 +1,299 @@ +#include "lowlevelfile.hpp" + +#include +#include +#include + +#if FILE_API == FILE_API_POSIX +#include +#include +#include +#endif + +#if FILE_API == FILE_API_STDIO +/* + * + * Implementation of LowLevelFile methods using c stdio + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = NULL; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle != NULL) + fclose (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == NULL); + + mHandle = fopen (filename, "rb"); + + if (mHandle == NULL) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } +} + +void LowLevelFile::close () +{ + assert (mHandle != NULL); + + fclose (mHandle); + + mHandle = NULL; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != NULL); + + long oldPosition = ftell (mHandle); + + if (oldPosition == -1) + throw std::runtime_error ("A query operation on a file failed."); + + if (fseek (mHandle, 0, SEEK_END) != 0) + throw std::runtime_error ("A query operation on a file failed."); + + long Size = ftell (mHandle); + + if (Size == -1) + throw std::runtime_error ("A query operation on a file failed."); + + if (fseek (mHandle, oldPosition, SEEK_SET) != 0) + throw std::runtime_error ("A query operation on a file failed."); + + return size_t (Size); +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != NULL); + + if (fseek (mHandle, Position, SEEK_SET) != 0) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != NULL); + + long Position = ftell (mHandle); + + if (Position == -1) + throw std::runtime_error ("A query operation on a file failed."); + + return size_t (Position); +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != NULL); + + int amount = fread (data, 1, size, mHandle); + + if (amount == 0 && ferror (mHandle)) + throw std::runtime_error ("A read operation on a file failed."); + + return amount; +} + +#elif FILE_API == FILE_API_POSIX +/* + * + * Implementation of LowLevelFile methods using posix IO calls + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = -1; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle != -1) + ::close (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == -1); + +#ifdef O_BINARY + static const int openFlags = O_RDONLY | O_BINARY; +#else + static const int openFlags = O_RDONLY; +#endif + + mHandle = ::open (filename, openFlags, 0); + + if (mHandle == -1) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } +} + +void LowLevelFile::close () +{ + assert (mHandle != -1); + + ::close (mHandle); + + mHandle = -1; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != -1); + + size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); + + if (oldPosition == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + size_t Size = ::lseek (mHandle, 0, SEEK_END); + + if (Size == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + if (lseek (mHandle, oldPosition, SEEK_SET) == -1) + throw std::runtime_error ("A query operation on a file failed."); + + return Size; +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != -1); + + if (::lseek (mHandle, Position, SEEK_SET) == -1) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != -1); + + size_t Position = ::lseek (mHandle, 0, SEEK_CUR); + + if (Position == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); + + return Position; +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != -1); + + int amount = ::read (mHandle, data, size); + + if (amount == -1) + throw std::runtime_error ("A read operation on a file failed."); + + return amount; +} + +#elif FILE_API == FILE_API_WIN32 +/* + * + * Implementation of LowLevelFile methods using Win32 API calls + * + */ + +LowLevelFile::LowLevelFile () +{ + mHandle = INVALID_HANDLE_VALUE; +} + +LowLevelFile::~LowLevelFile () +{ + if (mHandle == INVALID_HANDLE_VALUE) + CloseHandle (mHandle); +} + +void LowLevelFile::open (char const * filename) +{ + assert (mHandle == INVALID_HANDLE_VALUE); + + HANDLE handle = CreateFileA (filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + + if (handle == NULL) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } + + mHandle = handle; +} + +void LowLevelFile::close () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + CloseHandle (mHandle); + + mHandle = INVALID_HANDLE_VALUE; +} + +size_t LowLevelFile::size () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + BY_HANDLE_FILE_INFORMATION info; + + if (!GetFileInformationByHandle (mHandle, &info)) + throw std::runtime_error ("A query operation on a file failed."); + + if (info.nFileSizeHigh != 0) + throw std::runtime_error ("Files greater that 4GB are not supported."); + + return info.nFileSizeLow; +} + +void LowLevelFile::seek (size_t Position) +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (GetLastError () != NO_ERROR) + throw std::runtime_error ("A seek operation on a file failed."); +} + +size_t LowLevelFile::tell () +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR); + + if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) + throw std::runtime_error ("A query operation on a file failed."); + + return value; +} + +size_t LowLevelFile::read (void * data, size_t size) +{ + assert (mHandle != INVALID_HANDLE_VALUE); + + DWORD read; + + if (!ReadFile (mHandle, data, size, &read, NULL)) + throw std::runtime_error ("A read operation on a file failed."); + + return read; +} + +#endif diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp new file mode 100644 index 000000000..f49c466a5 --- /dev/null +++ b/components/files/lowlevelfile.hpp @@ -0,0 +1,56 @@ +#ifndef COMPONENTS_FILES_LOWLEVELFILE_HPP +#define COMPONENTS_FILES_LOWLEVELFILE_HPP + +#include + +#include + +#define FILE_API_STDIO 0 +#define FILE_API_POSIX 1 +#define FILE_API_WIN32 2 + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX +#define FILE_API FILE_API_POSIX +#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32 +#define FILE_API FILE_API_WIN32 +#else +#define FILE_API FILE_API_STDIO +#endif + +#if FILE_API == FILE_API_STDIO +#include +#elif FILE_API == FILE_API_POSIX +#elif FILE_API == FILE_API_WIN32 +#include +#else +#error Unsupported File API +#endif + +class LowLevelFile +{ +public: + + LowLevelFile (); + ~LowLevelFile (); + + void open (char const * filename); + void close (); + + size_t size (); + + void seek (size_t Position); + size_t tell (); + + size_t read (void * data, size_t size); + +private: +#if FILE_API == FILE_API_STDIO + FILE* mHandle; +#elif FILE_API == FILE_API_POSIX + int mHandle; +#elif FILE_API == FILE_API_WIN32 + HANDLE mHandle; +#endif +}; + +#endif diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 9625612ad..9edcd6ef2 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/macospath.cpp */ - #include "macospath.hpp" #if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp index 591c978aa..576ec1681 100644 --- a/components/files/macospath.hpp +++ b/components/files/macospath.hpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/macospath.hpp */ - #ifndef COMPONENTS_FILES_MACOSPATH_H #define COMPONENTS_FILES_MACOSPATH_H diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index b44c42986..347de96a6 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -95,6 +95,11 @@ namespace Files return iter->second; } + bool MultiDirCollection::doesExist (const std::string& file) const + { + return mFiles.find (file)!=mFiles.end(); + } + MultiDirCollection::TIter MultiDirCollection::begin() const { return mFiles.begin(); diff --git a/components/files/multidircollection.hpp b/components/files/multidircollection.hpp index e8abeb45d..3b420d677 100644 --- a/components/files/multidircollection.hpp +++ b/components/files/multidircollection.hpp @@ -73,6 +73,9 @@ namespace Files /// If the file does not exist, an exception is thrown. \a file must include /// the extension. + bool doesExist (const std::string& file) const; + ///< \return Does a file with the given name exist? + TIter begin() const; ///< Return iterator pointing to the first file. diff --git a/components/files/ogreplugin.cpp b/components/files/ogreplugin.cpp index ca90fd30e..c319f7758 100644 --- a/components/files/ogreplugin.cpp +++ b/components/files/ogreplugin.cpp @@ -6,11 +6,6 @@ namespace Files { bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { - // Append plugin suffix if debugging. -#if defined(DEBUG) - pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX; -#endif - #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE std::ostringstream verStream; verStream << "." << OGRE_VERSION_MAJOR << "." << OGRE_VERSION_MINOR << "." << OGRE_VERSION_PATCH; @@ -28,13 +23,28 @@ bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre:: pluginExt = ".so"; #endif - std::string pluginPath = pluginDir + "/" + pluginName + pluginExt; + // Append plugin suffix if debugging. + std::string pluginPath; +#if defined(DEBUG) + pluginPath = pluginDir + "/" + pluginName + OGRE_PLUGIN_DEBUG_SUFFIX + pluginExt; if (boost::filesystem::exists(pluginPath)) { - ogreRoot.loadPlugin(pluginPath); - return true; + ogreRoot.loadPlugin(pluginPath); + return true; } else { - return false; +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + return false; +#endif //OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + } +#endif //defined(DEBUG) + + pluginPath = pluginDir + "/" + pluginName + pluginExt; + if (boost::filesystem::exists(pluginPath)) { + ogreRoot.loadPlugin(pluginPath); + return true; + } + else { + return false; } } diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp index 2d56bfb47..6fcf61376 100644 --- a/components/files/ogreplugin.hpp +++ b/components/files/ogreplugin.hpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/ogreplugin.hpp */ - #ifndef COMPONENTS_FILES_OGREPLUGIN_H #define COMPONENTS_FILES_OGREPLUGIN_H diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp index 7fe8bc955..6044b67c2 100644 --- a/components/files/windowspath.hpp +++ b/components/files/windowspath.hpp @@ -1,25 +1,3 @@ -/** - * Open Morrowind - an opensource Elder Scrolls III: Morrowind - * engine implementation. - * - * Copyright (C) 2011 Open Morrowind Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file components/files/windowspath.hpp */ - #ifndef COMPONENTS_FILES_WINDOWSPATH_HPP #define COMPONENTS_FILES_WINDOWSPATH_HPP diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 4221da36e..bdba7b6af 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -49,6 +49,36 @@ namespace Interpreter virtual void setGlobalFloat (const std::string& name, float value) = 0; + virtual std::vector getGlobals () const = 0; + + virtual char getGlobalType (const std::string& name) const = 0; + + virtual std::string getActionBinding(const std::string& action) const = 0; + + virtual std::string getNPCName() const = 0; + + virtual std::string getNPCRace() const = 0; + + virtual std::string getNPCClass() const = 0; + + virtual std::string getNPCFaction() const = 0; + + virtual std::string getNPCRank() const = 0; + + virtual std::string getPCName() const = 0; + + virtual std::string getPCRace() const = 0; + + virtual std::string getPCClass() const = 0; + + virtual std::string getPCRank() const = 0; + + virtual std::string getPCNextRank() const = 0; + + virtual int getPCBounty() const = 0; + + virtual std::string getCurrentCellName() const = 0; + virtual bool isScriptRunning (const std::string& name) const = 0; virtual void startScript (const std::string& name) = 0; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp new file mode 100644 index 000000000..18e5f81c1 --- /dev/null +++ b/components/interpreter/defines.cpp @@ -0,0 +1,209 @@ +#include "defines.hpp" + +#include +#include +#include +#include + +namespace Interpreter{ + + bool Check(const std::string str, const std::string escword, unsigned int* i, unsigned int* start){ + bool retval = str.find(escword) == 0; + if(retval){ + (*i) += escword.length(); + (*start) = (*i) + 1; + } + return retval; + } + + std::vector globals; + + bool longerStr(const std::string a, const std::string b){ + return a.length() > b.length(); + } + + std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){ + + unsigned int start = 0; + std::ostringstream retval; + for(unsigned int i = 0; i < text.length(); i++){ + if(text[i] == eschar){ + retval << text.substr(start, i - start); + std::string temp = text.substr(i+1, 100); + transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + + bool found; + + if( (found = Check(temp, "actionslideright", &i, &start))){ + retval << context.getActionBinding("#{sRight}"); + } + else if((found = Check(temp, "actionreadymagic", &i, &start))){ + retval << context.getActionBinding("#{sReady_Magic}"); + } + else if((found = Check(temp, "actionprevweapon", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + } + else if((found = Check(temp, "actionnextweapon", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + } + else if((found = Check(temp, "actiontogglerun", &i, &start))){ + retval << context.getActionBinding("#{sAuto_Run}"); + } + else if((found = Check(temp, "actionslideleft", &i, &start))){ + retval << context.getActionBinding("#{sLeft}"); + } + else if((found = Check(temp, "actionreadyitem", &i, &start))){ + retval << context.getActionBinding("#{sReady_Weapon}"); + } + else if((found = Check(temp, "actionprevspell", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_SPELL"; + } + else if((found = Check(temp, "actionnextspell", &i, &start))){ + retval << "PLACEHOLDER_ACTION_NEXT_SPELL"; + } + else if((found = Check(temp, "actionrestmenu", &i, &start))){ + retval << context.getActionBinding("#{sRestKey}"); + } + else if((found = Check(temp, "actionmenumode", &i, &start))){ + retval << context.getActionBinding("#{sJournal}"); + } + else if((found = Check(temp, "actionactivate", &i, &start))){ + retval << context.getActionBinding("#{sActivate}"); + } + else if((found = Check(temp, "actionjournal", &i, &start))){ + retval << context.getActionBinding("#{sJournal}"); + } + else if((found = Check(temp, "actionforward", &i, &start))){ + retval << context.getActionBinding("#{sForward}"); + } + else if((found = Check(temp, "pccrimelevel", &i, &start))){ + retval << context.getPCBounty(); + } + else if((found = Check(temp, "actioncrouch", &i, &start))){ + retval << context.getActionBinding("#{sCrouch_Sneak}"); + } + else if((found = Check(temp, "actionjump", &i, &start))){ + retval << context.getActionBinding("#{sJump}"); + } + else if((found = Check(temp, "actionback", &i, &start))){ + retval << context.getActionBinding("#{sBack}"); + } + else if((found = Check(temp, "actionuse", &i, &start))){ + retval << "PLACEHOLDER_ACTION_USE"; + } + else if((found = Check(temp, "actionrun", &i, &start))){ + retval << "PLACEHOLDER_ACTION_RUN"; + } + else if((found = Check(temp, "pcclass", &i, &start))){ + retval << context.getPCClass(); + } + else if((found = Check(temp, "pcrace", &i, &start))){ + retval << context.getPCRace(); + } + else if((found = Check(temp, "pcname", &i, &start))){ + retval << context.getPCName(); + } + else if((found = Check(temp, "cell", &i, &start))){ + retval << context.getCurrentCellName(); + } + + else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + if( (found = Check(temp, "faction", &i, &start))){ + retval << context.getNPCFaction(); + } + else if((found = Check(temp, "nextpcrank", &i, &start))){ + retval << context.getPCNextRank(); + } + else if((found = Check(temp, "pcnextrank", &i, &start))){ + retval << context.getPCNextRank(); + } + else if((found = Check(temp, "pcrank", &i, &start))){ + retval << context.getPCRank(); + } + else if((found = Check(temp, "rank", &i, &start))){ + retval << context.getNPCRank(); + } + + else if((found = Check(temp, "class", &i, &start))){ + retval << context.getNPCClass(); + } + else if((found = Check(temp, "race", &i, &start))){ + retval << context.getNPCRace(); + } + else if((found = Check(temp, "name", &i, &start))){ + retval << context.getNPCName(); + } + } + else { // In messagebox or book, not dialogue + + /* empty outside dialogue */ + if( (found = Check(temp, "faction", &i, &start))); + else if((found = Check(temp, "nextpcrank", &i, &start))); + else if((found = Check(temp, "pcnextrank", &i, &start))); + else if((found = Check(temp, "pcrank", &i, &start))); + else if((found = Check(temp, "rank", &i, &start))); + + /* uses pc in messageboxes */ + else if((found = Check(temp, "class", &i, &start))){ + retval << context.getPCClass(); + } + else if((found = Check(temp, "race", &i, &start))){ + retval << context.getPCRace(); + } + else if((found = Check(temp, "name", &i, &start))){ + retval << context.getPCName(); + } + } + + /* Not a builtin, try global variables */ + if(!found){ + /* if list of globals is empty, grab it and sort it by descending string length */ + if(globals.empty()){ + globals = context.getGlobals(); + sort(globals.begin(), globals.end(), longerStr); + } + + for(unsigned int j = 0; j < globals.size(); j++){ + if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name + std::string temp = text.substr(i+1, globals[j].length()); + transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + } + + if((found = Check(temp, globals[j], &i, &start))){ + char type = context.getGlobalType(globals[j]); + + switch(type){ + case 's': retval << context.getGlobalShort(globals[j]); break; + case 'l': retval << context.getGlobalLong(globals[j]); break; + case 'f': retval << context.getGlobalFloat(globals[j]); break; + } + break; + } + } + } + + /* Not found */ + if(!found){ + /* leave unmodified */ + i += 1; + start = i; + retval << eschar; + } + } + } + retval << text.substr(start, text.length() - start); + return retval.str (); + } + + std::string fixDefinesDialog(std::string text, Context& context){ + return fixDefinesReal(text, '%', false, context); + } + + std::string fixDefinesMsgBox(std::string text, Context& context){ + return fixDefinesReal(text, '^', false, context); + } + + std::string fixDefinesBook(std::string text, Context& context){ + return fixDefinesReal(text, '%', true, context); + } +} diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp new file mode 100644 index 000000000..00c4386b8 --- /dev/null +++ b/components/interpreter/defines.hpp @@ -0,0 +1,13 @@ +#ifndef INTERPRETER_DEFINES_H_INCLUDED +#define INTERPRETER_DEFINES_H_INCLUDED + +#include +#include "context.hpp" + +namespace Interpreter{ + std::string fixDefinesDialog(std::string text, Context& context); + std::string fixDefinesMsgBox(std::string text, Context& context); + std::string fixDefinesBook(std::string text, Context& context); +} + +#endif diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 37c38fc30..1b4c823a0 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -10,6 +10,7 @@ #include "opcodes.hpp" #include "runtime.hpp" +#include "defines.hpp" namespace Interpreter { @@ -69,7 +70,8 @@ namespace Interpreter } } } - + + formattedMessage = fixDefinesMsgBox(formattedMessage, runtime.getContext()); return formattedMessage; } diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 53eed1fdc..0bc8e290a 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -1,8 +1,14 @@ #include "stringops.hpp" +#include +#include +#include + #include #include + + namespace Misc { diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index a32104bf3..029b617e1 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -1,8 +1,59 @@ #ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H +#include +#include +#include + namespace Misc { +class StringUtils +{ + struct ci + { + bool operator()(int x, int y) const { + return std::tolower(x) < std::tolower(y); + } + }; + +public: + static bool ciLess(const std::string &x, const std::string &y) { + return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); + } + + static bool ciEqual(const std::string &x, const std::string &y) { + if (x.size() != y.size()) { + return false; + } + std::string::const_iterator xit = x.begin(); + std::string::const_iterator yit = y.begin(); + for (; xit != x.end(); ++xit, ++yit) { + if (std::tolower(*xit) != std::tolower(*yit)) { + return false; + } + } + return true; + } + + /// Transforms input string to lower case w/o copy + static std::string &toLower(std::string &inout) { + std::transform( + inout.begin(), + inout.end(), + inout.begin(), + (int (*)(int)) std::tolower + ); + return inout; + } + + /// Returns lower case copy of input string + static std::string lowerCase(const std::string &in) + { + std::string out = in; + return toLower(out); + } +}; + /// Returns true if str1 begins with substring str2 bool begins(const char* str1, const char* str2); diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index b9d84b58a..36c9a82ac 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_CONTROLLED_H_ -#define _NIF_CONTROLLED_H_ +#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP +#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP #include "extra.hpp" #include "controller.hpp" @@ -36,7 +36,7 @@ class Controlled : public Extra public: ControllerPtr controller; - void read(NIFFile *nif) + void read(NIFStream *nif) { Extra::read(nif); controller.read(nif); @@ -55,7 +55,7 @@ class Named : public Controlled public: std::string name; - void read(NIFFile *nif) + void read(NIFStream *nif) { name = nif->getString(); Controlled::read(nif); @@ -66,7 +66,7 @@ typedef Named NiSequenceStreamHelper; class NiParticleGrowFade : public Controlled { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Controlled::read(nif); @@ -80,7 +80,7 @@ class NiParticleColorModifier : public Controlled public: NiColorDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controlled::read(nif); data.read(nif); @@ -96,7 +96,7 @@ public: class NiGravity : public Controlled { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Controlled::read(nif); @@ -109,7 +109,7 @@ public: class NiPlanarCollider : public Controlled { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Controlled::read(nif); @@ -121,7 +121,7 @@ public: class NiParticleRotation : public Controlled { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Controlled::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index cbc19cd8f..8331b93b7 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -21,12 +21,12 @@ */ -#ifndef _NIF_CONTROLLER_H_ -#define _NIF_CONTROLLER_H_ +#ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP +#define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP #include "record.hpp" -#include "nif_file.hpp" -#include "record_ptr.hpp" +#include "niffile.hpp" +#include "recordptr.hpp" namespace Nif { @@ -40,7 +40,7 @@ public: float timeStart, timeStop; ControlledPtr target; - void read(NIFFile *nif) + void read(NIFStream *nif) { next.read(nif); @@ -65,7 +65,7 @@ public: class NiBSPArrayController : public Controller { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); @@ -82,7 +82,7 @@ class NiMaterialColorController : public Controller public: NiPosDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); data.read(nif); @@ -101,7 +101,7 @@ public: NiPosDataPtr posData; NiFloatDataPtr floatData; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); @@ -129,7 +129,7 @@ class NiUVController : public Controller public: NiUVDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); @@ -149,7 +149,7 @@ class NiKeyframeController : public Controller public: NiKeyframeDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); data.read(nif); @@ -167,7 +167,7 @@ class NiAlphaController : public Controller public: NiFloatDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); data.read(nif); @@ -185,7 +185,7 @@ class NiGeomMorpherController : public Controller public: NiMorphDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); data.read(nif); @@ -204,7 +204,7 @@ class NiVisController : public Controller public: NiVisDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Controller::read(nif); data.read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 63df23b27..9bdba6396 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_DATA_H_ -#define _NIF_DATA_H_ +#ifndef OPENMW_COMPONENTS_NIF_DATA_HPP +#define OPENMW_COMPONENTS_NIF_DATA_HPP #include "controlled.hpp" @@ -65,7 +65,7 @@ public: */ int alpha; - void read(NIFFile *nif) + void read(NIFStream *nif) { Named::read(nif); @@ -102,7 +102,7 @@ public: Ogre::Vector3 center; float radius; - void read(NIFFile *nif) + void read(NIFStream *nif) { int verts = nif->getUShort(); @@ -138,24 +138,22 @@ public: // Triangles, three vertex indices per triangle std::vector triangles; - void read(NIFFile *nif) + void read(NIFStream *nif) { ShapeData::read(nif); - int tris = nif->getUShort(); - if(tris) - { - // We have three times as many vertices as triangles, so this - // is always equal to tris*3. - int cnt = nif->getInt(); - nif->getShorts(triangles, cnt); - } + /*int tris =*/ nif->getUShort(); + + // We have three times as many vertices as triangles, so this + // is always equal to tris*3. + int cnt = nif->getInt(); + nif->getShorts(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;igetUShort(); @@ -169,7 +167,7 @@ class NiAutoNormalParticlesData : public ShapeData public: int activeCount; - void read(NIFFile *nif) + void read(NIFStream *nif) { ShapeData::read(nif); @@ -191,7 +189,7 @@ public: class NiRotatingParticlesData : public NiAutoNormalParticlesData { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { NiAutoNormalParticlesData::read(nif); @@ -210,7 +208,7 @@ class NiPosData : public Record public: Vector3KeyList mKeyList; - void read(NIFFile *nif) + void read(NIFStream *nif) { mKeyList.read(nif); } @@ -221,7 +219,7 @@ class NiUVData : public Record public: FloatKeyList mKeyList[4]; - void read(NIFFile *nif) + void read(NIFStream *nif) { for(int i = 0;i < 4;i++) mKeyList[i].read(nif); @@ -233,7 +231,7 @@ class NiFloatData : public Record public: FloatKeyList mKeyList; - void read(NIFFile *nif) + void read(NIFStream *nif) { mKeyList.read(nif); } @@ -245,7 +243,7 @@ public: unsigned int rmask, gmask, bmask, amask; int bpp, mips; - void read(NIFFile *nif) + void read(NIFStream *nif) { nif->getInt(); // always 0 or 1 @@ -283,7 +281,7 @@ class NiColorData : public Record public: Vector4KeyList mKeyList; - void read(NIFFile *nif) + void read(NIFStream *nif) { mKeyList.read(nif); } @@ -297,7 +295,7 @@ public: char isSet; }; - void read(NIFFile *nif) + void read(NIFStream *nif) { int count = nif->getInt(); @@ -313,7 +311,7 @@ public: NodePtr root; NodeList bones; - void read(NIFFile *nif) + void read(NIFStream *nif) { data.read(nif); root.read(nif); @@ -349,7 +347,7 @@ public: BoneTrafo trafo; std::vector bones; - void read(NIFFile *nif) + void read(NIFStream *nif) { trafo.rotation = nif->getMatrix3(); trafo.trans = nif->getVector3(); @@ -387,7 +385,7 @@ struct NiMorphData : public Record }; std::vector mMorphs; - void read(NIFFile *nif) + void read(NIFStream *nif) { int morphCount = nif->getInt(); int vertCount = nif->getInt(); @@ -412,7 +410,7 @@ struct NiKeyframeData : public Record Vector3KeyList mTranslations; FloatKeyList mScales; - void read(NIFFile *nif) + void read(NIFStream *nif) { mRotations.read(nif); mTranslations.read(nif); diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 850415dad..cc1b0f41c 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_EFFECT_H_ -#define _NIF_EFFECT_H_ +#ifndef OPENMW_COMPONENTS_NIF_EFFECT_HPP +#define OPENMW_COMPONENTS_NIF_EFFECT_HPP #include "node.hpp" @@ -42,7 +42,7 @@ struct NiLight : Effect Ogre::Vector3 diffuse; Ogre::Vector3 specular; - void read(NIFFile *nif) + void read(NIFStream *nif) { dimmer = nif->getFloat(); ambient = nif->getVector3(); @@ -52,7 +52,7 @@ struct NiLight : Effect }; SLight light; - void read(NIFFile *nif) + void read(NIFStream *nif) { Effect::read(nif); @@ -66,7 +66,7 @@ struct NiTextureEffect : Effect { NiSourceTexturePtr texture; - void read(NIFFile *nif) + void read(NIFStream *nif) { Effect::read(nif); diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 35781dbf5..45c4fefc6 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -21,12 +21,12 @@ */ -#ifndef _NIF_EXTRA_H_ -#define _NIF_EXTRA_H_ +#ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP +#define OPENMW_COMPONENTS_NIF_EXTRA_HPP #include "record.hpp" -#include "nif_file.hpp" -#include "record_ptr.hpp" +#include "niffile.hpp" +#include "recordptr.hpp" namespace Nif { @@ -40,14 +40,14 @@ class Extra : public Record public: ExtraPtr extra; - void read(NIFFile *nif) { extra.read(nif); } + void read(NIFStream *nif) { extra.read(nif); } void post(NIFFile *nif) { extra.post(nif); } }; class NiVertWeightsExtraData : public Extra { public: - void read(NIFFile *nif) + void read(NIFStream *nif) { Extra::read(nif); @@ -70,7 +70,7 @@ public: }; std::vector list; - void read(NIFFile *nif) + void read(NIFStream *nif) { Extra::read(nif); @@ -95,7 +95,7 @@ public: */ std::string string; - void read(NIFFile *nif) + void read(NIFStream *nif) { Extra::read(nif); diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp deleted file mode 100644 index 3313d89ab..000000000 --- a/components/nif/nif_file.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (nif_file.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#include "nif_file.hpp" -#include "record.hpp" -#include "components/misc/stringops.hpp" - -#include "extra.hpp" -#include "controlled.hpp" -#include "node.hpp" -#include "property.hpp" -#include "data.hpp" -#include "effect.hpp" -#include "controller.hpp" - -#include -using namespace std; -using namespace Nif; -using namespace Misc; - -/* This file implements functions from the NIFFile class. It is also - where we stash all the functions we couldn't add as inline - definitions in the record types. - */ - -void NIFFile::parse() -{ - // Check the header string - std::string head = getString(40); - if(head.compare(0, 22, "NetImmerse File Format") != 0) - fail("Invalid NIF header"); - - // Get BCD version - ver = getInt(); - if(ver != VER_MW) - fail("Unsupported NIF version"); - - // Number of records - int recNum = getInt(); - records.resize(recNum); - - /* The format for 10.0.1.0 seems to be a bit different. After the - header, it contains the number of records, r (int), just like - 4.0.0.2, but following that it contains a short x, followed by x - strings. Then again by r shorts, one for each record, giving - which of the above strings to use to identify the record. After - this follows two ints (zero?) and then the record data. However - we do not support or plan to support other versions yet. - */ - - for(int i=0;irecType = RC_NiNode; } - - // Other nodes - else if(rec == "NiTriShape") { r = new NiTriShape; r->recType = RC_NiTriShape; } - else if(rec == "NiRotatingParticles") { r = new NiRotatingParticles; r->recType = RC_NiRotatingParticles; } - else if(rec == "NiAutoNormalParticles") { r = new NiAutoNormalParticles; r->recType = RC_NiAutoNormalParticles; } - else if(rec == "NiCamera") { r = new NiCamera; r->recType = RC_NiCamera; } - else if(rec == "RootCollisionNode"){ r = new NiNode; r->recType = RC_RootCollisionNode; }// a root collision node is exactly like a node - //that's why there is no need to create a new type - - // Properties - else if(rec == "NiTexturingProperty") { r = new NiTexturingProperty; r->recType = RC_NiTexturingProperty; } - else if(rec == "NiMaterialProperty") { r = new NiMaterialProperty; r->recType = RC_NiMaterialProperty; } - else if(rec == "NiZBufferProperty") { r = new NiZBufferProperty; r->recType = RC_NiZBufferProperty; } - else if(rec == "NiAlphaProperty") { r = new NiAlphaProperty; r->recType = RC_NiAlphaProperty; } - else if(rec == "NiVertexColorProperty") { r = new NiVertexColorProperty; r->recType = RC_NiVertexColorProperty; } - else if(rec == "NiShadeProperty") { r = new NiShadeProperty; r->recType = RC_NiShadeProperty; } - else if(rec == "NiDitherProperty") { r = new NiDitherProperty; r->recType = RC_NiDitherProperty; } - else if(rec == "NiWireframeProperty") { r = new NiWireframeProperty; r->recType = RC_NiWireframeProperty; } - else if(rec == "NiSpecularProperty") { r = new NiSpecularProperty; r->recType = RC_NiSpecularProperty; } - - // Controllers - else if(rec == "NiVisController") { r = new NiVisController; r->recType = RC_NiVisController; } - else if(rec == "NiGeomMorpherController") { r = new NiGeomMorpherController; r->recType = RC_NiGeomMorpherController; } - else if(rec == "NiKeyframeController") { r = new NiKeyframeController; r->recType = RC_NiKeyframeController; } - else if(rec == "NiAlphaController") { r = new NiAlphaController; r->recType = RC_NiAlphaController; } - else if(rec == "NiUVController") { r = new NiUVController; r->recType = RC_NiUVController; } - else if(rec == "NiPathController") { r = new NiPathController; r->recType = RC_NiPathController; } - else if(rec == "NiMaterialColorController") { r = new NiMaterialColorController; r->recType = RC_NiMaterialColorController; } - else if(rec == "NiBSPArrayController") { r = new NiBSPArrayController; r->recType = RC_NiBSPArrayController; } - else if(rec == "NiParticleSystemController") { r = new NiParticleSystemController; r->recType = RC_NiParticleSystemController; } - - // Effects - else if(rec == "NiAmbientLight" || - rec == "NiDirectionalLight") { r = new NiLight; r->recType = RC_NiLight; } - else if(rec == "NiTextureEffect") { r = new NiTextureEffect; r->recType = RC_NiTextureEffect; } - - // Extra Data - else if(rec == "NiVertWeightsExtraData") { r = new NiVertWeightsExtraData; r->recType = RC_NiVertWeightsExtraData; } - else if(rec == "NiTextKeyExtraData") { r = new NiTextKeyExtraData; r->recType = RC_NiTextKeyExtraData; } - else if(rec == "NiStringExtraData") { r = new NiStringExtraData; r->recType = RC_NiStringExtraData; } - - else if(rec == "NiGravity") { r = new NiGravity; r->recType = RC_NiGravity; } - else if(rec == "NiPlanarCollider") { r = new NiPlanarCollider; r->recType = RC_NiPlanarCollider; } - else if(rec == "NiParticleGrowFade") { r = new NiParticleGrowFade; r->recType = RC_NiParticleGrowFade; } - else if(rec == "NiParticleColorModifier") { r = new NiParticleColorModifier; r->recType = RC_NiParticleColorModifier; } - else if(rec == "NiParticleRotation") { r = new NiParticleRotation; r->recType = RC_NiParticleRotation; } - - // Data - else if(rec == "NiFloatData") { r = new NiFloatData; r->recType = RC_NiFloatData; } - else if(rec == "NiTriShapeData") { r = new NiTriShapeData; r->recType = RC_NiTriShapeData; } - else if(rec == "NiVisData") { r = new NiVisData; r->recType = RC_NiVisData; } - else if(rec == "NiColorData") { r = new NiColorData; r->recType = RC_NiColorData; } - else if(rec == "NiPixelData") { r = new NiPixelData; r->recType = RC_NiPixelData; } - else if(rec == "NiMorphData") { r = new NiMorphData; r->recType = RC_NiMorphData; } - else if(rec == "NiKeyframeData") { r = new NiKeyframeData; r->recType = RC_NiKeyframeData; } - else if(rec == "NiSkinData") { r = new NiSkinData; r->recType = RC_NiSkinData; } - else if(rec == "NiUVData") { r = new NiUVData; r->recType = RC_NiUVData; } - else if(rec == "NiPosData") { r = new NiPosData; r->recType = RC_NiPosData; } - else if(rec == "NiRotatingParticlesData") { r = new NiRotatingParticlesData; r->recType = RC_NiRotatingParticlesData; } - else if(rec == "NiAutoNormalParticlesData") { r = new NiAutoNormalParticlesData; r->recType = RC_NiAutoNormalParticlesData; } - - // Other - else if(rec == "NiSequenceStreamHelper") { r = new NiSequenceStreamHelper; r->recType = RC_NiSequenceStreamHelper; } - else if(rec == "NiSourceTexture") { r = new NiSourceTexture; r->recType = RC_NiSourceTexture; } - else if(rec == "NiSkinInstance") { r = new NiSkinInstance; r->recType = RC_NiSkinInstance; } - - // Failure - else - fail("Unknown record type " + rec); - - assert(r != NULL); - assert(r->recType != RC_MISSING); - r->recName = rec; - records[i] = r; - r->read(this); - - // Discard tranformations for the root node, otherwise some meshes - // occasionally get wrong orientation. Only for NiNode-s for now, but - // can be expanded if needed. - // This should be rewritten when the method is cleaned up. - if (0 == i && rec == "NiNode") - { - static_cast(r)->trafo = Nif::Transformation::getIdentity(); - } - } - - /* After the data, the nif contains an int N and then a list of N - ints following it. This might be a list of the root nodes in the - tree, but for the moment we ignore it. - */ - - // TODO: Set up kf file here first, if applicable. It needs its own - // code to link it up with the main NIF structure. - - // Once parsing is done, do post-processing. - for(int i=0; ipost(this); -} - -/// \todo move to the write cpp file - -void NiSkinInstance::post(NIFFile *nif) -{ - data.post(nif); - root.post(nif); - bones.post(nif); - - if(data.empty() || root.empty()) - nif->fail("NiSkinInstance missing root or data"); - - size_t bnum = bones.length(); - if(bnum != data->bones.size()) - nif->fail("Mismatch in NiSkinData bone count"); - - root->makeRootBone(&data->trafo); - - for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); - bones[i]->makeBone(i, data->bones[i]); - } -} - -Ogre::Matrix4 Node::getLocalTransform() -{ - Ogre::Matrix4 mat4(Ogre::Matrix4::IDENTITY); - mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation)); - return mat4; -} - -Ogre::Matrix4 Node::getWorldTransform() -{ - if(parent != NULL) - return parent->getWorldTransform() * getLocalTransform(); - return getLocalTransform(); -} diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp deleted file mode 100644 index 23bccb0fe..000000000 --- a/components/nif/nif_file.hpp +++ /dev/null @@ -1,318 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (nif_file.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#ifndef _NIF_FILE_H_ -#define _NIF_FILE_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "record.hpp" -#include "nif_types.hpp" - -namespace Nif -{ - -class NIFFile -{ - enum NIFVersion { - VER_MW = 0x04000002 // Morrowind NIFs - }; - - /// Nif file version - int ver; - - /// Input stream - Ogre::DataStreamPtr inp; - - /// File name, used for error messages - std::string filename; - - /// Record list - std::vector records; - - /// Parse the file - void parse(); - - uint8_t read_byte() - { - uint8_t byte; - if(inp->read(&byte, 1) != 1) return 0; - return byte; - } - uint16_t read_le16() - { - uint8_t buffer[2]; - if(inp->read(buffer, 2) != 2) return 0; - return buffer[0] | (buffer[1]<<8); - } - uint32_t read_le32() - { - uint8_t buffer[4]; - if(inp->read(buffer, 4) != 4) return 0; - return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); - } - float read_le32f() - { - union { - uint32_t i; - float f; - } u = { read_le32() }; - return u.f; - } - -public: - /// Used for error handling - void fail(const std::string &msg) - { - std::string err = "NIFFile Error: " + msg; - err += "\nFile: " + filename; - throw std::runtime_error(err); - } - - void warn(const std::string &msg) - { - std::cerr<< "NIFFile Warning: "<skip(size); } - - char getChar() { return read_byte(); } - short getShort() { return read_le16(); } - unsigned short getUShort() { return read_le16(); } - int getInt() { return read_le32(); } - float getFloat() { return read_le32f(); } - Ogre::Vector2 getVector2() - { - float a[2]; - for(size_t i = 0;i < 2;i++) - a[i] = getFloat(); - return Ogre::Vector2(a); - } - Ogre::Vector3 getVector3() - { - float a[3]; - for(size_t i = 0;i < 3;i++) - a[i] = getFloat(); - return Ogre::Vector3(a); - } - Ogre::Vector4 getVector4() - { - float a[4]; - for(size_t i = 0;i < 4;i++) - a[i] = getFloat(); - return Ogre::Vector4(a); - } - Ogre::Matrix3 getMatrix3() - { - Ogre::Real a[3][3]; - for(size_t i = 0;i < 3;i++) - { - for(size_t j = 0;j < 3;j++) - a[i][j] = Ogre::Real(getFloat()); - } - return Ogre::Matrix3(a); - } - Ogre::Quaternion getQuaternion() - { - float a[4]; - for(size_t i = 0;i < 4;i++) - a[i] = getFloat(); - return Ogre::Quaternion(a); - } - Transformation getTrafo() - { - Transformation t; - t.pos = getVector3(); - t.rotation = getMatrix3(); - t.scale = getFloat(); - return t; - } - - std::string getString(size_t length) - { - std::vector str (length+1, 0); - - if(inp->read(&str[0], length) != length) - throw std::runtime_error ("string length in NIF file does not match"); - - return &str[0]; - } - std::string getString() - { - size_t size = read_le32(); - return getString(size); - } - - void getShorts(std::vector &vec, size_t size) - { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getShort(); - } - void getFloats(std::vector &vec, size_t size) - { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getFloat(); - } - void getVector2s(std::vector &vec, size_t size) - { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector2(); - } - void getVector3s(std::vector &vec, size_t size) - { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector3(); - } - void getVector4s(std::vector &vec, size_t size) - { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector4(); - } -}; - - -template -struct KeyT { - float mTime; - T mValue; - T mForwardValue; // Only for Quadratic interpolation - T mBackwardValue; // Only for Quadratic interpolation - float mTension; // Only for TBC interpolation - float mBias; // Only for TBC interpolation - float mContinuity; // Only for TBC interpolation -}; -typedef KeyT FloatKey; -typedef KeyT Vector3Key; -typedef KeyT Vector4Key; -typedef KeyT QuaternionKey; - -template -struct KeyListT { - typedef std::vector< KeyT > VecType; - - static const int sLinearInterpolation = 1; - static const int sQuadraticInterpolation = 2; - static const int sTBCInterpolation = 3; - - int mInterpolationType; - VecType mKeys; - - void read(NIFFile *nif, bool force=false) - { - size_t count = nif->getInt(); - if(count == 0 && !force) - return; - - mInterpolationType = nif->getInt(); - mKeys.resize(count); - if(mInterpolationType == sLinearInterpolation) - { - for(size_t i = 0;i < count;i++) - { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); - } - } - else if(mInterpolationType == sQuadraticInterpolation) - { - for(size_t i = 0;i < count;i++) - { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); - key.mForwardValue = (nif->*getValue)(); - key.mBackwardValue = (nif->*getValue)(); - } - } - else if(mInterpolationType == sTBCInterpolation) - { - for(size_t i = 0;i < count;i++) - { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); - key.mTension = nif->getFloat(); - key.mBias = nif->getFloat(); - key.mContinuity = nif->getFloat(); - } - } - else - nif->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); - } -}; -typedef KeyListT FloatKeyList; -typedef KeyListT Vector3KeyList; -typedef KeyListT Vector4KeyList; -typedef KeyListT QuaternionKeyList; - -} // Namespace -#endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp new file mode 100644 index 000000000..bf05e7576 --- /dev/null +++ b/components/nif/niffile.cpp @@ -0,0 +1,397 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (nif_file.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + */ + +#include "niffile.hpp" +#include "record.hpp" +#include "components/misc/stringops.hpp" + +#include "extra.hpp" +#include "controlled.hpp" +#include "node.hpp" +#include "property.hpp" +#include "data.hpp" +#include "effect.hpp" +#include "controller.hpp" + +#include + +//TODO: when threading is needed, enable these +//#include +#include + +namespace Nif +{ + +class NIFFile::LoadedCache +{ + //TODO: enable this to make cache thread safe... + //typedef boost::mutex mutex; + + struct mutex + { + void lock () {}; + void unlock () {} + }; + + typedef boost::lock_guard lock_guard; + typedef std::map < std::string, boost::weak_ptr > loaded_map; + typedef std::vector < boost::shared_ptr > locked_files; + + static int sLockLevel; + static mutex sProtector; + static loaded_map sLoadedMap; + static locked_files sLockedFiles; + +public: + + static ptr create (const std::string &name) + { + lock_guard _ (sProtector); + + ptr result; + + // lookup the resource + loaded_map::iterator i = sLoadedMap.find (name); + + if (i == sLoadedMap.end ()) // it doesn't existing currently, + { // or hasn't in the very near past + + // create it now, for smoother threading if needed, the + // loading should be performed outside of the sLoaderMap + // lock and an alternate mechanism should be used to + // synchronize threads competing to load the same resource + result = boost::make_shared (name, psudo_private_modifier()); + + // if we are locking the cache add an extra reference + // to keep the file in memory + if (sLockLevel > 0) + sLockedFiles.push_back (result); + + // stash a reference to the resource so that future + // calls can benefit + sLoadedMap [name] = boost::weak_ptr (result); + } + else // it may (probably) still exists + { + // attempt to get the reference + result = i->second.lock (); + + if (!result) // resource is in the process of being destroyed + { + // create a new instance, to replace the one that has + // begun the irreversible process of being destroyed + result = boost::make_shared (name, psudo_private_modifier()); + + // respect the cache lock... + if (sLockLevel > 0) + sLockedFiles.push_back (result); + + // we potentially overwrite an expired pointer here + // but the other thread performing the delete on + // the previous copy of this resource will detect it + // and make sure not to erase the new reference + sLoadedMap [name] = boost::weak_ptr (result); + } + } + + // we made it! + return result; + } + + static void release (NIFFile * file) + { + lock_guard _ (sProtector); + + loaded_map::iterator i = sLoadedMap.find (file->filename); + + // its got to be in here, it just might not be us... + assert (i != sLoadedMap.end ()); + + // if weak_ptr is still expired, this resource hasn't been recreated + // between the initiation of the final release due to destruction + // of the last shared pointer and this thread acquiring the lock on + // the loader map + if (i->second.expired ()) + sLoadedMap.erase (i); + } + + static void lockCache () + { + lock_guard _ (sProtector); + + sLockLevel++; + } + + static void unlockCache () + { + locked_files resetList; + + { + lock_guard _ (sProtector); + + if (--sLockLevel) + sLockedFiles.swap(resetList); + } + + // this not necessary, but makes it clear that the + // deletion of the locked cache entries is being done + // outside the protection of sProtector + resetList.clear (); + } +}; + +int NIFFile::LoadedCache::sLockLevel = 0; +NIFFile::LoadedCache::mutex NIFFile::LoadedCache::sProtector; +NIFFile::LoadedCache::loaded_map NIFFile::LoadedCache::sLoadedMap; +NIFFile::LoadedCache::locked_files NIFFile::LoadedCache::sLockedFiles; + +// these three calls are forwarded to the cache implementation... +void NIFFile::lockCache () { LoadedCache::lockCache (); } +void NIFFile::unlockCache () { LoadedCache::unlockCache (); } +NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::create (name); } + +/// Open a NIF stream. The name is used for error messages. +NIFFile::NIFFile(const std::string &name, psudo_private_modifier) + : filename(name) +{ + parse(); +} + +NIFFile::~NIFFile() +{ + LoadedCache::release (this); + + for(std::size_t i=0; i static Record* construct() { return new NodeType; } + +struct RecordFactoryEntry { + + typedef Record* (*create_t) (); + + char const * mName; + create_t mCreate; + RecordType mType; + +}; + +/* These are all the record types we know how to read. + + This can be heavily optimized later if needed. For example, a + hash table or a FSM-based parser could be used to look up + node names. +*/ + +static const RecordFactoryEntry recordFactories [] = { + + { "NiNode", &construct , RC_NiNode }, + { "AvoidNode", &construct , RC_NiNode }, + { "NiBSParticleNode", &construct , RC_NiNode }, + { "NiBSAnimationNode", &construct , RC_NiNode }, + { "NiBillboardNode", &construct , RC_NiNode }, + { "NiTriShape", &construct , RC_NiTriShape }, + { "NiRotatingParticles", &construct , RC_NiRotatingParticles }, + { "NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles }, + { "NiCamera", &construct , RC_NiCamera }, + { "RootCollisionNode", &construct , RC_RootCollisionNode }, + { "NiTexturingProperty", &construct , RC_NiTexturingProperty }, + { "NiMaterialProperty", &construct , RC_NiMaterialProperty }, + { "NiZBufferProperty", &construct , RC_NiZBufferProperty }, + { "NiAlphaProperty", &construct , RC_NiAlphaProperty }, + { "NiVertexColorProperty", &construct , RC_NiVertexColorProperty }, + { "NiShadeProperty", &construct , RC_NiShadeProperty }, + { "NiDitherProperty", &construct , RC_NiDitherProperty }, + { "NiWireframeProperty", &construct , RC_NiWireframeProperty }, + { "NiSpecularProperty", &construct , RC_NiSpecularProperty }, + { "NiStencilProperty", &construct , RC_NiStencilProperty }, + { "NiVisController", &construct , RC_NiVisController }, + { "NiGeomMorpherController", &construct , RC_NiGeomMorpherController }, + { "NiKeyframeController", &construct , RC_NiKeyframeController }, + { "NiAlphaController", &construct , RC_NiAlphaController }, + { "NiUVController", &construct , RC_NiUVController }, + { "NiPathController", &construct , RC_NiPathController }, + { "NiMaterialColorController", &construct , RC_NiMaterialColorController }, + { "NiBSPArrayController", &construct , RC_NiBSPArrayController }, + { "NiParticleSystemController", &construct , RC_NiParticleSystemController }, + { "NiAmbientLight", &construct , RC_NiLight }, + { "NiDirectionalLight", &construct , RC_NiLight }, + { "NiTextureEffect", &construct , RC_NiTextureEffect }, + { "NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData }, + { "NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData }, + { "NiStringExtraData", &construct , RC_NiStringExtraData }, + { "NiGravity", &construct , RC_NiGravity }, + { "NiPlanarCollider", &construct , RC_NiPlanarCollider }, + { "NiParticleGrowFade", &construct , RC_NiParticleGrowFade }, + { "NiParticleColorModifier", &construct , RC_NiParticleColorModifier }, + { "NiParticleRotation", &construct , RC_NiParticleRotation }, + { "NiFloatData", &construct , RC_NiFloatData }, + { "NiTriShapeData", &construct , RC_NiTriShapeData }, + { "NiVisData", &construct , RC_NiVisData }, + { "NiColorData", &construct , RC_NiColorData }, + { "NiPixelData", &construct , RC_NiPixelData }, + { "NiMorphData", &construct , RC_NiMorphData }, + { "NiKeyframeData", &construct , RC_NiKeyframeData }, + { "NiSkinData", &construct , RC_NiSkinData }, + { "NiUVData", &construct , RC_NiUVData }, + { "NiPosData", &construct , RC_NiPosData }, + { "NiRotatingParticlesData", &construct , RC_NiRotatingParticlesData }, + { "NiAutoNormalParticlesData", &construct , RC_NiAutoNormalParticlesData }, + { "NiSequenceStreamHelper", &construct , RC_NiSequenceStreamHelper }, + { "NiSourceTexture", &construct , RC_NiSourceTexture }, + { "NiSkinInstance", &construct , RC_NiSkinInstance }, +}; + +static RecordFactoryEntry const * recordFactories_begin = &recordFactories [0]; +static RecordFactoryEntry const * recordFactories_end = &recordFactories [sizeof (recordFactories) / sizeof (recordFactories[0])]; + +RecordFactoryEntry const * lookupRecordFactory (char const * name) +{ + RecordFactoryEntry const * i; + + for (i = recordFactories_begin; i != recordFactories_end; ++i) + if (strcmp (name, i->mName) == 0) + break; + + if (i == recordFactories_end) + return NULL; + + return i; +} + +/* This file implements functions from the NIFFile class. It is also + where we stash all the functions we couldn't add as inline + definitions in the record types. + */ + +void NIFFile::parse() +{ + NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename)); + + // Check the header string + std::string head = nif.getString(40); + if(head.compare(0, 22, "NetImmerse File Format") != 0) + fail("Invalid NIF header"); + + // Get BCD version + ver = nif.getInt(); + if(ver != VER_MW) + fail("Unsupported NIF version"); + + // Number of records + size_t recNum = nif.getInt(); + records.resize(recNum); + + /* The format for 10.0.1.0 seems to be a bit different. After the + header, it contains the number of records, r (int), just like + 4.0.0.2, but following that it contains a short x, followed by x + strings. Then again by r shorts, one for each record, giving + which of the above strings to use to identify the record. After + this follows two ints (zero?) and then the record data. However + we do not support or plan to support other versions yet. + */ + + for(size_t i = 0;i < recNum;i++) + { + Record *r = NULL; + + std::string rec = nif.getString(); + + RecordFactoryEntry const * entry = lookupRecordFactory (rec.c_str ()); + + if (entry != NULL) + { + r = entry->mCreate (); + r->recType = entry->mType; + } + else + fail("Unknown record type " + rec); + + assert(r != NULL); + assert(r->recType != RC_MISSING); + r->recName = rec; + r->recIndex = i; + records[i] = r; + r->read(&nif); + + // Discard tranformations for the root node, otherwise some meshes + // occasionally get wrong orientation. Only for NiNode-s for now, but + // can be expanded if needed. + // This should be rewritten when the method is cleaned up. + if (0 == i && rec == "NiNode") + { + static_cast(r)->trafo = Nif::Transformation::getIdentity(); + } + } + + /* After the data, the nif contains an int N and then a list of N + ints following it. This might be a list of the root nodes in the + tree, but for the moment we ignore it. + */ + + // Once parsing is done, do post-processing. + for(size_t i=0; ipost(this); +} + +/// \todo move to the write cpp file + +void NiSkinInstance::post(NIFFile *nif) +{ + data.post(nif); + root.post(nif); + bones.post(nif); + + if(data.empty() || root.empty()) + nif->fail("NiSkinInstance missing root or data"); + + size_t bnum = bones.length(); + if(bnum != data->bones.size()) + nif->fail("Mismatch in NiSkinData bone count"); + + root->makeRootBone(&data->trafo); + + for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); + bones[i]->makeBone(i, data->bones[i]); + } +} + +Ogre::Matrix4 Node::getLocalTransform() const +{ + Ogre::Matrix4 mat4(Ogre::Matrix4::IDENTITY); + mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation)); + return mat4; +} + +Ogre::Matrix4 Node::getWorldTransform() const +{ + if(parent != NULL) + return parent->getWorldTransform() * getLocalTransform(); + return getLocalTransform(); +} + +} diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp new file mode 100644 index 000000000..ed11bdd7c --- /dev/null +++ b/components/nif/niffile.hpp @@ -0,0 +1,200 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (nif_file.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + */ + +#ifndef OPENMW_COMPONENTS_NIF_NIFFILE_HPP +#define OPENMW_COMPONENTS_NIF_NIFFILE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "record.hpp" +#include "niftypes.hpp" +#include "nifstream.hpp" + +namespace Nif +{ + +class NIFFile +{ + enum NIFVersion { + VER_MW = 0x04000002 // Morrowind NIFs + }; + + /// Nif file version + int ver; + + /// File name, used for error messages + std::string filename; + + /// Record list + std::vector records; + + /// Parse the file + void parse(); + + class LoadedCache; + friend class LoadedCache; + + // attempt to protect NIFFile from misuse... + struct psudo_private_modifier {}; // this dirty little trick should optimize out + NIFFile (NIFFile const &); + void operator = (NIFFile const &); + +public: + /// Used for error handling + void fail(const std::string &msg) + { + std::string err = "NIFFile Error: " + msg; + err += "\nFile: " + filename; + throw std::runtime_error(err); + } + + void warn(const std::string &msg) + { + std::cerr << "NIFFile Warning: " << msg < ptr; + + /// Open a NIF stream. The name is used for error messages. + NIFFile(const std::string &name, psudo_private_modifier); + ~NIFFile(); + + static ptr create (const std::string &name); + static void lockCache (); + static void unlockCache (); + + struct CacheLock + { + CacheLock () { lockCache (); } + ~CacheLock () { unlockCache (); } + }; + + /// Get a given record + Record *getRecord(size_t index) + { + Record *res = records.at(index); + assert(res != NULL); + return res; + } + + /// Number of records + size_t numRecords() { return records.size(); } +}; + + +template +struct KeyT { + float mTime; + T mValue; + T mForwardValue; // Only for Quadratic interpolation + T mBackwardValue; // Only for Quadratic interpolation + float mTension; // Only for TBC interpolation + float mBias; // Only for TBC interpolation + float mContinuity; // Only for TBC interpolation +}; +typedef KeyT FloatKey; +typedef KeyT Vector3Key; +typedef KeyT Vector4Key; +typedef KeyT QuaternionKey; + +template +struct KeyListT { + typedef std::vector< KeyT > VecType; + + static const int sLinearInterpolation = 1; + static const int sQuadraticInterpolation = 2; + static const int sTBCInterpolation = 3; + + int mInterpolationType; + VecType mKeys; + + void read(NIFStream *nif, bool force=false) + { + size_t count = nif->getInt(); + if(count == 0 && !force) + return; + + mInterpolationType = nif->getInt(); + mKeys.resize(count); + if(mInterpolationType == sLinearInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + } + } + else if(mInterpolationType == sQuadraticInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + key.mForwardValue = (nif->*getValue)(); + key.mBackwardValue = (nif->*getValue)(); + } + } + else if(mInterpolationType == sTBCInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + key.mTension = nif->getFloat(); + key.mBias = nif->getFloat(); + key.mContinuity = nif->getFloat(); + } + } + else + nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + } +}; +typedef KeyListT FloatKeyList; +typedef KeyListT Vector3KeyList; +typedef KeyListT Vector4KeyList; +typedef KeyListT QuaternionKeyList; + +} // Namespace +#endif diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp new file mode 100644 index 000000000..02b931b7e --- /dev/null +++ b/components/nif/nifstream.hpp @@ -0,0 +1,175 @@ +#ifndef OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP +#define OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP + +namespace Nif +{ + +class NIFFile; + +class NIFStream { + + /// Input stream + Ogre::DataStreamPtr inp; + + uint8_t read_byte() + { + uint8_t byte; + if(inp->read(&byte, 1) != 1) return 0; + return byte; + } + uint16_t read_le16() + { + uint8_t buffer[2]; + if(inp->read(buffer, 2) != 2) return 0; + return buffer[0] | (buffer[1]<<8); + } + uint32_t read_le32() + { + uint8_t buffer[4]; + if(inp->read(buffer, 4) != 4) return 0; + return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); + } + float read_le32f() + { + union { + uint32_t i; + float f; + } u = { read_le32() }; + return u.f; + } + +public: + + NIFFile * const file; + + NIFStream (NIFFile * file, Ogre::DataStreamPtr inp): file (file), inp (inp) {} + + /************************************************* + Parser functions + ****************************************************/ + + template + struct GetHandler + { + typedef T (NIFStream::*fn_t)(); + + static const fn_t sValue; // this is specialized per supported type in the .cpp file + + static T read (NIFStream* nif) + { + return (nif->*sValue) (); + } + }; + + template + void read (NIFStream* nif, T & Value) + { + Value = GetHandler ::read (nif); + } + + void skip(size_t size) { inp->skip(size); } + void read (void * data, size_t size) { inp->read (data, size); } + + char getChar() { return read_byte(); } + short getShort() { return read_le16(); } + unsigned short getUShort() { return read_le16(); } + int getInt() { return read_le32(); } + int getUInt() { return read_le32(); } + float getFloat() { return read_le32f(); } + Ogre::Vector2 getVector2() + { + float a[2]; + for(size_t i = 0;i < 2;i++) + a[i] = getFloat(); + return Ogre::Vector2(a); + } + Ogre::Vector3 getVector3() + { + float a[3]; + for(size_t i = 0;i < 3;i++) + a[i] = getFloat(); + return Ogre::Vector3(a); + } + Ogre::Vector4 getVector4() + { + float a[4]; + for(size_t i = 0;i < 4;i++) + a[i] = getFloat(); + return Ogre::Vector4(a); + } + Ogre::Matrix3 getMatrix3() + { + Ogre::Real a[3][3]; + for(size_t i = 0;i < 3;i++) + { + for(size_t j = 0;j < 3;j++) + a[i][j] = Ogre::Real(getFloat()); + } + return Ogre::Matrix3(a); + } + Ogre::Quaternion getQuaternion() + { + float a[4]; + for(size_t i = 0;i < 4;i++) + a[i] = getFloat(); + return Ogre::Quaternion(a); + } + Transformation getTrafo() + { + Transformation t; + t.pos = getVector3(); + t.rotation = getMatrix3(); + t.scale = getFloat(); + return t; + } + + std::string getString(size_t length) + { + std::vector str (length+1, 0); + + if(inp->read(&str[0], length) != length) + throw std::runtime_error ("string length in NIF file does not match"); + + return &str[0]; + } + std::string getString() + { + size_t size = read_le32(); + return getString(size); + } + + void getShorts(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getShort(); + } + void getFloats(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getFloat(); + } + void getVector2s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector2(); + } + void getVector3s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector3(); + } + void getVector4s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector4(); + } +}; + +} + +#endif diff --git a/components/nif/nif_types.hpp b/components/nif/niftypes.hpp similarity index 93% rename from components/nif/nif_types.hpp rename to components/nif/niftypes.hpp index a5fb61361..786c48b65 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/niftypes.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_TYPES_H_ -#define _NIF_TYPES_H_ +#ifndef OPENMW_COMPONENTS_NIF_NIFTYPES_HPP +#define OPENMW_COMPONENTS_NIF_NIFTYPES_HPP #include #include diff --git a/components/nif/node.hpp b/components/nif/node.hpp index f7d3c6e96..ab92d74f8 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_NODE_H_ -#define _NIF_NODE_H_ +#ifndef OPENMW_COMPONENTS_NIF_NODE_HPP +#define OPENMW_COMPONENTS_NIF_NODE_HPP #include @@ -54,7 +54,7 @@ public: Ogre::Matrix3 boundRot; Ogre::Vector3 boundXYZ; // Box size - void read(NIFFile *nif) + void read(NIFStream *nif) { Named::read(nif); @@ -111,8 +111,8 @@ public: boneIndex = ind; } - Ogre::Matrix4 getLocalTransform(); - Ogre::Matrix4 getWorldTransform(); + Ogre::Matrix4 getLocalTransform() const; + Ogre::Matrix4 getWorldTransform() const; }; struct NiNode : Node @@ -128,7 +128,7 @@ struct NiNode : Node 0x20, 0x40, 0x80 unknown */ - void read(NIFFile *nif) + void read(NIFStream *nif) { Node::read(nif); children.read(nif); @@ -162,7 +162,7 @@ struct NiTriShape : Node NiTriShapeDataPtr data; NiSkinInstancePtr skin; - void read(NIFFile *nif) + void read(NIFStream *nif) { Node::read(nif); data.read(nif); @@ -190,7 +190,7 @@ struct NiCamera : Node // Level of detail modifier float LOD; - void read(NIFFile *nif) + void read(NIFStream *nif) { left = nif->getFloat(); right = nif->getFloat(); @@ -209,7 +209,7 @@ struct NiCamera : Node }; Camera cam; - void read(NIFFile *nif) + void read(NIFStream *nif) { Node::read(nif); @@ -224,7 +224,7 @@ struct NiAutoNormalParticles : Node { NiAutoNormalParticlesDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Node::read(nif); data.read(nif); @@ -242,7 +242,7 @@ struct NiRotatingParticles : Node { NiRotatingParticlesDataPtr data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Node::read(nif); data.read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index b24e49b47..cd1e0a5d1 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_PROPERTY_H_ -#define _NIF_PROPERTY_H_ +#ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP +#define OPENMW_COMPONENTS_NIF_PROPERTY_HPP #include "controlled.hpp" @@ -35,7 +35,7 @@ public: // The meaning of these depends on the actual property type. int flags; - void read(NIFFile *nif) + void read(NIFStream *nif) { Named::read(nif); flags = nif->getUShort(); @@ -67,7 +67,7 @@ public: int clamp, set, filter; short unknown2; - void read(NIFFile *nif) + void read(NIFStream *nif) { inUse = !!nif->getInt(); if(!inUse) return; @@ -111,7 +111,7 @@ public: */ Texture textures[7]; - void read(NIFFile *nif) + void read(NIFStream *nif) { Property::read(nif); apply = nif->getInt(); @@ -157,7 +157,7 @@ struct StructPropT : Property { T data; - void read(NIFFile *nif) + void read(NIFStream *nif) { Property::read(nif); data.read(nif); @@ -170,7 +170,7 @@ struct S_MaterialProperty Ogre::Vector3 ambient, diffuse, specular, emissive; float glossiness, alpha; - void read(NIFFile *nif) + void read(NIFStream *nif) { ambient = nif->getVector3(); diffuse = nif->getVector3(); @@ -194,7 +194,7 @@ struct S_VertexColorProperty */ int vertmode, lightmode; - void read(NIFFile *nif) + void read(NIFStream *nif) { vertmode = nif->getInt(); lightmode = nif->getInt(); @@ -251,15 +251,72 @@ struct S_AlphaProperty // Tested against when certain flags are set (see above.) unsigned char threshold; - void read(NIFFile *nif) + void read(NIFStream *nif) { threshold = nif->getChar(); } }; +/* + Docs taken from: + http://niftools.sourceforge.net/doc/nif/NiStencilProperty.html + */ +struct S_StencilProperty +{ + // Is stencil test enabled? + unsigned char enabled; + + /* + 0 TEST_NEVER + 1 TEST_LESS + 2 TEST_EQUAL + 3 TEST_LESS_EQUAL + 4 TEST_GREATER + 5 TEST_NOT_EQUAL + 6 TEST_GREATER_EQUAL + 7 TEST_ALWAYS + */ + int compareFunc; + unsigned stencilRef; + unsigned stencilMask; + /* + Stencil test fail action, depth test fail action and depth test pass action: + 0 ACTION_KEEP + 1 ACTION_ZERO + 2 ACTION_REPLACE + 3 ACTION_INCREMENT + 4 ACTION_DECREMENT + 5 ACTION_INVERT + */ + int failAction; + int zFailAction; + int zPassAction; + /* + Face draw mode: + 0 DRAW_CCW_OR_BOTH + 1 DRAW_CCW [default] + 2 DRAW_CW + 3 DRAW_BOTH + */ + int drawMode; + + void 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(); + } +}; + typedef StructPropT NiAlphaProperty; typedef StructPropT NiMaterialProperty; typedef StructPropT NiVertexColorProperty; +typedef StructPropT NiStencilProperty; } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 5c4141fa9..3a3cd9b84 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -21,8 +21,8 @@ */ -#ifndef _NIF_RECORD_H_ -#define _NIF_RECORD_H_ +#ifndef OPENMW_COMPONENTS_NIF_RECORD_HPP +#define OPENMW_COMPONENTS_NIF_RECORD_HPP #include @@ -30,6 +30,7 @@ namespace Nif { class NIFFile; +class NIFStream; enum RecordType { @@ -48,6 +49,7 @@ enum RecordType RC_NiDitherProperty, RC_NiWireframeProperty, RC_NiSpecularProperty, + RC_NiStencilProperty, RC_NiVisController, RC_NiGeomMorpherController, RC_NiKeyframeController, @@ -91,11 +93,12 @@ struct Record // Record type and type name int recType; std::string recName; + size_t recIndex; - Record() : recType(RC_MISSING) {} + Record() : recType(RC_MISSING), recIndex(~(size_t)0) {} /// Parses the record from file - virtual void read(NIFFile *nif) = 0; + virtual void read(NIFStream *nif) = 0; /// Does post-processing, after the entire tree is loaded virtual void post(NIFFile *nif) {} diff --git a/components/nif/record_ptr.hpp b/components/nif/recordptr.hpp similarity index 95% rename from components/nif/record_ptr.hpp rename to components/nif/recordptr.hpp index ef5bb1dee..c5bafea12 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/recordptr.hpp @@ -21,10 +21,10 @@ */ -#ifndef _NIF_RECORD_PTR_H_ -#define _NIF_RECORD_PTR_H_ +#ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP +#define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP -#include "nif_file.hpp" +#include "niffile.hpp" #include namespace Nif @@ -46,7 +46,7 @@ public: RecordPtrT() : index(-2) {} /// Read the index from the nif - void read(NIFFile *nif) + void read(NIFStream *nif) { // Can only read the index once assert(index == -2); @@ -99,7 +99,7 @@ class RecordListT std::vector list; public: - void read(NIFFile *nif) + void read(NIFStream *nif) { int len = nif->getInt(); list.resize(len); diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bulletnifloader.cpp similarity index 62% rename from components/nifbullet/bullet_nif_loader.cpp rename to components/nifbullet/bulletnifloader.cpp index 42f6a8e68..91fd6dbcf 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -21,11 +21,11 @@ http://www.gnu.org/licenses/ . */ -#include "bullet_nif_loader.hpp" +#include "bulletnifloader.hpp" #include #include -#include "../nif/nif_file.hpp" +#include "../nif/niffile.hpp" #include "../nif/node.hpp" #include "../nif/data.hpp" #include "../nif/property.hpp" @@ -43,26 +43,15 @@ http://www.gnu.org/licenses/ . typedef unsigned char ubyte; -using namespace NifBullet; - +namespace NifBullet +{ ManualBulletShapeLoader::~ManualBulletShapeLoader() { } -btQuaternion ManualBulletShapeLoader::getbtQuat(Ogre::Matrix3 &m) -{ - Ogre::Quaternion oquat(m); - btQuaternion quat; - quat.setW(oquat.w); - quat.setX(oquat.x); - quat.setY(oquat.y); - quat.setZ(oquat.z); - return quat; -} - -btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 &v) +btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 const &v) { return btVector3(v[0], v[1], v[2]); } @@ -82,14 +71,14 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) // of the early stages of development. Right now we WANT to catch // every error as early and intrusively as possible, as it's most // likely a sign of incomplete code rather than faulty input. - Nif::NIFFile nif(resourceName.substr(0, resourceName.length()-7)); + Nif::NIFFile::ptr pnif (Nif::NIFFile::create (resourceName.substr(0, resourceName.length()-7))); + Nif::NIFFile & nif = *pnif.get (); if (nif.numRecords() < 1) { warn("Found no records in NIF."); return; } - // The first record is assumed to be the root node Nif::Record *r = nif.getRecord(0); assert(r != NULL); @@ -105,13 +94,11 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) bool hasCollisionNode = hasRootCollisionNode(node); //do a first pass - handleNode(node,0,NULL,hasCollisionNode,false,false); + handleNode(node,0,hasCollisionNode,false,false); //if collide = false, then it does a second pass which create a shape for raycasting. if(cShape->mCollide == false) - { - handleNode(node,0,NULL,hasCollisionNode,false,true); - } + handleNode(node,0,hasCollisionNode,false,true); //cShape->collide = hasCollisionNode&&cShape->collide; @@ -128,9 +115,9 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) delete m_meshInterface; } }; + if(mBoundingBox != NULL) cShape->Shape = mBoundingBox; - else { currentShape = new TriangleMeshShape(mTriMesh,true); @@ -138,40 +125,38 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) } } -bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node* node) +bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) { - if (node->recType == Nif::RC_NiNode) + if(node->recType == Nif::RC_RootCollisionNode) + return true; + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) { - Nif::NodeList &list = ((Nif::NiNode*)node)->children; - int n = list.length(); - for (int i=0; ichildren; + for(size_t i = 0;i < list.length();i++) { - if (!list[i].empty()) + if(!list[i].empty()) { - if(hasRootCollisionNode(list[i].getPtr())) return true;; + if(hasRootCollisionNode(list[i].getPtr())) + return true; } } } - else if (node->recType == Nif::RC_NiTriShape) - { - return false; - } - else if(node->recType == Nif::RC_RootCollisionNode) - { - return true; - } return false; } -void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, - const Nif::Transformation *trafo,bool hasCollisionNode,bool isCollisionNode,bool raycastingOnly) +void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, + bool hasCollisionNode, bool isCollisionNode, + bool raycastingOnly) { - // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node->flags; + isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); + // Marker objects: no collision /// \todo don't do this in the editor if (node->name.find("marker") != std::string::npos) @@ -181,7 +166,7 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, } // Check for extra data - Nif::Extra *e = node; + Nif::Extra const *e = node; while (!e->extra.empty()) { // Get the next extra data in the list @@ -208,70 +193,37 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, } } - - if (trafo) + if(!hasCollisionNode || isCollisionNode) { - - // Get a non-const reference to the node's data, since we're - // overwriting it. TODO: Is this necessary? - Nif::Transformation &final = node->trafo; - - // For both position and rotation we have that: - // final_vector = old_vector + old_rotation*new_vector*old_scale - final.pos = trafo->pos + trafo->rotation*final.pos*trafo->scale; - - // Merge the rotations together - final.rotation = trafo->rotation * final.rotation; - - // Scale - final.scale *= trafo->scale; - - } - - if(node->hasBounds) - { - - - btVector3 boxsize = getbtVector((node->boundXYZ)); - cShape->boxTranslation = node->boundPos; - cShape->boxRotation = node->boundRot; - - mBoundingBox = new btBoxShape(boxsize); - } - - - // For NiNodes, loop through children - if (node->recType == Nif::RC_NiNode) - { - Nif::NodeList &list = ((Nif::NiNode*)node)->children; - int n = list.length(); - for (int i=0; ihasBounds) { - if (!list[i].empty()) - { - handleNode(list[i].getPtr(), flags,&node->trafo,hasCollisionNode,isCollisionNode,raycastingOnly); - } + cShape->boxTranslation = node->boundPos; + cShape->boxRotation = node->boundRot; + mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ)); + } + + if(node->recType == Nif::RC_NiTriShape) + { + cShape->mCollide = !(flags&0x800); + handleNiTriShape(static_cast(node), flags, node->getWorldTransform(), raycastingOnly); } } - else if (node->recType == Nif::RC_NiTriShape && (isCollisionNode || !hasCollisionNode)) + + // For NiNodes, loop through children + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) { - cShape->mCollide = !(flags&0x800); - handleNiTriShape(dynamic_cast(node), flags,node->trafo.rotation,node->trafo.pos,node->trafo.scale,raycastingOnly); - } - else if(node->recType == Nif::RC_RootCollisionNode) - { - Nif::NodeList &list = ((Nif::NiNode*)node)->children; - int n = list.length(); - for (int i=0; ichildren; + for(size_t i = 0;i < list.length();i++) { - if (!list[i].empty()) - handleNode(list[i].getPtr(), flags,&node->trafo, hasCollisionNode,true,raycastingOnly); + if(!list[i].empty()) + handleNode(list[i].getPtr(), flags, hasCollisionNode, isCollisionNode, raycastingOnly); } } } -void ManualBulletShapeLoader::handleNiTriShape(Nif::NiTriShape *shape, int flags,Ogre::Matrix3 parentRot,Ogre::Vector3 parentPos,float parentScale, - bool raycastingOnly) +void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, + bool raycastingOnly) { assert(shape != NULL); @@ -295,18 +247,14 @@ void ManualBulletShapeLoader::handleNiTriShape(Nif::NiTriShape *shape, int flags return; - Nif::NiTriShapeData *data = shape->data.getPtr(); - + const Nif::NiTriShapeData *data = shape->data.getPtr(); const std::vector &vertices = data->vertices; - const Ogre::Matrix3 &rot = shape->trafo.rotation; - const Ogre::Vector3 &pos = shape->trafo.pos; - float scale = shape->trafo.scale * parentScale; - short* triangles = &data->triangles[0]; + const short *triangles = &data->triangles[0]; for(size_t i = 0;i < data->triangles.size();i+=3) { - Ogre::Vector3 b1 = pos + rot*vertices[triangles[i+0]]*scale; - Ogre::Vector3 b2 = pos + rot*vertices[triangles[i+1]]*scale; - Ogre::Vector3 b3 = pos + rot*vertices[triangles[i+2]]*scale; + Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; + Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; + Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; mTriMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); } } @@ -319,3 +267,5 @@ void ManualBulletShapeLoader::load(const std::string &name,const std::string &gr return; OEngine::Physic::BulletShapeManager::getSingleton().create(name,group,true,this); } + +} // namespace NifBullet diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bulletnifloader.hpp similarity index 81% rename from components/nifbullet/bullet_nif_loader.hpp rename to components/nifbullet/bulletnifloader.hpp index 2190fda1b..0ff4b4ccd 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -21,8 +21,8 @@ */ -#ifndef _BULLET_NIF_LOADER_H_ -#define _BULLET_NIF_LOADER_H_ +#ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP +#define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #include #include @@ -51,19 +51,18 @@ namespace NifBullet class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader { public: - ManualBulletShapeLoader():resourceGroup("General"){} virtual ~ManualBulletShapeLoader(); - void warn(std::string msg) + void warn(const std::string &msg) { std::cerr << "NIFLoader: Warn:" << msg << "\n"; } - void fail(std::string msg) + void fail(const std::string &msg) { std::cerr << "NIFLoader: Fail: "<< msg << std::endl; - assert(1); + abort(); } /** @@ -79,31 +78,26 @@ public: void load(const std::string &name,const std::string &group); private: - btQuaternion getbtQuat(Ogre::Matrix3 &m); - - btVector3 getbtVector(Ogre::Vector3 &v); + btVector3 getbtVector(Ogre::Vector3 const &v); /** *Parse a node. */ - void handleNode(Nif::Node *node, int flags, - const Nif::Transformation *trafo, bool hasCollisionNode,bool isCollisionNode,bool raycastingOnly); + void handleNode(Nif::Node const *node, int flags, bool hasCollisionNode, bool isCollisionNode, bool raycastingOnly); /** *Helper function */ - bool hasRootCollisionNode(Nif::Node* node); + bool hasRootCollisionNode(const Nif::Node *node); /** *convert a NiTriShape to a bullet trishape. */ - void handleNiTriShape(Nif::NiTriShape *shape, int flags,Ogre::Matrix3 parentRot,Ogre::Vector3 parentPos,float parentScales,bool raycastingOnly); + void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycastingOnly); std::string resourceName; std::string resourceGroup; - - OEngine::Physic::BulletShape* cShape;//current shape btTriangleMesh *mTriMesh; btBoxShape *mBoundingBox; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp deleted file mode 100644 index ad51d50b9..000000000 --- a/components/nifogre/ogre_nif_loader.cpp +++ /dev/null @@ -1,1218 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (ogre_nif_loader.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -//loadResource->handleNode->handleNiTriShape->createSubMesh - -#include "ogre_nif_loader.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -typedef unsigned char ubyte; - -using namespace std; -using namespace Nif; -using namespace NifOgre; - - -// Helper class that computes the bounding box and of a mesh -class BoundsFinder -{ - struct MaxMinFinder - { - float max, min; - - MaxMinFinder() - { - min = numeric_limits::infinity(); - max = -min; - } - - void add(float f) - { - if (f > max) max = f; - if (f < min) min = f; - } - - // Return Max(max**2, min**2) - float getMaxSquared() - { - float m1 = max*max; - float m2 = min*min; - if (m1 >= m2) return m1; - return m2; - } - }; - - MaxMinFinder X, Y, Z; - -public: - // Add 'verts' vertices to the calculation. The 'data' pointer is - // expected to point to 3*verts floats representing x,y,z for each - // point. - void add(float *data, int verts) - { - for (int i=0;ilist.size();i++) - { - const std::string &str = tk->list[i].text; - std::string::size_type pos = 0; - while(pos < str.length()) - { - while(pos < str.length() && ::isspace(str[pos])) - pos++; - if(pos >= str.length()) - break; - - std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); - textkeys->insert(std::make_pair(tk->list[i].time, str.substr(pos, nextpos-pos))); - - pos = nextpos; - } - } -} - - -void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, std::vector &ctrls, Ogre::Bone *parent=NULL) -{ - Ogre::Bone *bone; - if(!skel->hasBone(node->name)) - bone = skel->createBone(node->name); - else - bone = skel->createBone(); - if(parent) parent->addChild(bone); - - bone->setOrientation(node->trafo.rotation); - bone->setPosition(node->trafo.pos); - bone->setScale(Ogre::Vector3(node->trafo.scale)); - bone->setBindingPose(); - bone->setInitialState(); - - Nif::ControllerPtr ctrl = node->controller; - while(!ctrl.empty()) - { - if(ctrl->recType == Nif::RC_NiKeyframeController) - ctrls.push_back(static_cast(ctrl.getPtr())); - ctrl = ctrl->next; - } - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - buildBones(skel, children[i].getPtr(), ctrls, bone); - } - } -} - - -/* Comparitor to help sort Key<> vectors */ -template -struct KeyTimeSort -{ - bool operator()(const Nif::KeyT &lhs, const Nif::KeyT &rhs) const - { return lhs.mTime < rhs.mTime; } -}; - - -typedef std::map LoaderMap; -static LoaderMap sLoaders; - -public: -void loadResource(Ogre::Resource *resource) -{ - Ogre::Skeleton *skel = dynamic_cast(resource); - OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); - - Nif::NIFFile nif(skel->getName()); - const Nif::Node *node = dynamic_cast(nif.getRecord(0)); - - std::vector ctrls; - buildBones(skel, node, ctrls); - - std::vector targets; - // TODO: If ctrls.size() == 0, check for a .kf file sharing the name of the .nif file - if(ctrls.size() == 0) // No animations? Then we're done. - return; - - float maxtime = 0.0f; - for(size_t i = 0;i < ctrls.size();i++) - { - Nif::NiKeyframeController *ctrl = ctrls[i]; - maxtime = std::max(maxtime, ctrl->timeStop); - Nif::Named *target = dynamic_cast(ctrl->target.getPtr()); - if(target != NULL) - targets.push_back(target->name); - } - - if(targets.size() != ctrls.size()) - { - warn("Target size mismatch ("+Ogre::StringConverter::toString(targets.size())+" targets, "+ - Ogre::StringConverter::toString(ctrls.size())+" controllers)"); - return; - } - - Ogre::Animation *anim = skel->createAnimation(skel->getName(), maxtime); - /* HACK: Pre-create the node tracks by matching the track IDs with the - * bone IDs. Otherwise, Ogre animates the wrong bones. */ - size_t bonecount = skel->getNumBones(); - for(size_t i = 0;i < bonecount;i++) - anim->createNodeTrack(i, skel->getBone(i)); - - for(size_t i = 0;i < ctrls.size();i++) - { - Nif::NiKeyframeController *kfc = ctrls[i]; - Nif::NiKeyframeData *kf = kfc->data.getPtr(); - - /* Get the keyframes and make sure they're sorted first to last */ - QuaternionKeyList quatkeys = kf->mRotations; - Vector3KeyList trankeys = kf->mTranslations; - FloatKeyList scalekeys = kf->mScales; - std::sort(quatkeys.mKeys.begin(), quatkeys.mKeys.end(), KeyTimeSort()); - std::sort(trankeys.mKeys.begin(), trankeys.mKeys.end(), KeyTimeSort()); - std::sort(scalekeys.mKeys.begin(), scalekeys.mKeys.end(), KeyTimeSort()); - - QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin(); - Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin(); - FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin(); - - Ogre::Bone *bone = skel->getBone(targets[i]); - const Ogre::Quaternion startquat = bone->getInitialOrientation(); - const Ogre::Vector3 starttrans = bone->getInitialPosition(); - const Ogre::Vector3 startscale = bone->getInitialScale(); - Ogre::NodeAnimationTrack *nodetrack = anim->getNodeTrack(bone->getHandle()); - - Ogre::Quaternion lastquat, curquat; - Ogre::Vector3 lasttrans(0.0f), curtrans(0.0f); - Ogre::Vector3 lastscale(1.0f), curscale(1.0f); - if(quatiter != quatkeys.mKeys.end()) - lastquat = curquat = startquat.Inverse() * quatiter->mValue; - if(traniter != trankeys.mKeys.end()) - lasttrans = curtrans = traniter->mValue - starttrans; - if(scaleiter != scalekeys.mKeys.end()) - lastscale = curscale = Ogre::Vector3(scaleiter->mValue) / startscale; - bool didlast = false; - - while(!didlast) - { - float curtime = kfc->timeStop; - - //Get latest time - if(quatiter != quatkeys.mKeys.end()) - curtime = std::min(curtime, quatiter->mTime); - if(traniter != trankeys.mKeys.end()) - curtime = std::min(curtime, traniter->mTime); - if(scaleiter != scalekeys.mKeys.end()) - curtime = std::min(curtime, scaleiter->mTime); - - curtime = std::max(curtime, kfc->timeStart); - if(curtime >= kfc->timeStop) - { - didlast = true; - curtime = kfc->timeStop; - } - - // Get the latest quaternions, translations, and scales for the - // current time - while(quatiter != quatkeys.mKeys.end() && curtime >= quatiter->mTime) - { - lastquat = curquat; - quatiter++; - if(quatiter != quatkeys.mKeys.end()) - curquat = startquat.Inverse() * quatiter->mValue ; - } - while(traniter != trankeys.mKeys.end() && curtime >= traniter->mTime) - { - lasttrans = curtrans; - traniter++; - if(traniter != trankeys.mKeys.end()) - curtrans = traniter->mValue - starttrans; - } - while(scaleiter != scalekeys.mKeys.end() && curtime >= scaleiter->mTime) - { - lastscale = curscale; - scaleiter++; - if(scaleiter != scalekeys.mKeys.end()) - curscale = Ogre::Vector3(scaleiter->mValue) / startscale; - } - - Ogre::TransformKeyFrame *kframe; - kframe = nodetrack->createNodeKeyFrame(curtime); - if(quatiter == quatkeys.mKeys.end() || quatiter == quatkeys.mKeys.begin()) - kframe->setRotation(curquat); - else - { - QuaternionKeyList::VecType::const_iterator last = quatiter-1; - float diff = (curtime-last->mTime) / (quatiter->mTime-last->mTime); - kframe->setRotation(Ogre::Quaternion::nlerp(diff, lastquat, curquat)); - } - if(traniter == trankeys.mKeys.end() || traniter == trankeys.mKeys.begin()) - kframe->setTranslate(curtrans); - else - { - Vector3KeyList::VecType::const_iterator last = traniter-1; - float diff = (curtime-last->mTime) / (traniter->mTime-last->mTime); - kframe->setTranslate(lasttrans + ((curtrans-lasttrans)*diff)); - } - if(scaleiter == scalekeys.mKeys.end() || scaleiter == scalekeys.mKeys.begin()) - kframe->setScale(curscale); - else - { - FloatKeyList::VecType::const_iterator last = scaleiter-1; - float diff = (curtime-last->mTime) / (scaleiter->mTime-last->mTime); - kframe->setScale(lastscale + ((curscale-lastscale)*diff)); - } - } - } - anim->optimise(); -} - -bool createSkeleton(const std::string &name, const std::string &group, TextKeyMap *textkeys, const Nif::Node *node) -{ - if(textkeys) - { - Nif::ExtraPtr e = node->extra; - while(!e.empty()) - { - if(e->recType == Nif::RC_NiTextKeyExtraData) - { - const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - insertTextKeys(tk, textkeys); - } - e = e->extra; - } - } - - if(node->boneTrafo != NULL) - { - Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); - - Ogre::SkeletonPtr skel = skelMgr.getByName(name); - if(skel.isNull()) - { - NIFSkeletonLoader *loader = &sLoaders[name]; - skel = skelMgr.create(name, group, true, loader); - } - - if(!textkeys || textkeys->size() > 0) - return true; - } - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - { - if(createSkeleton(name, group, textkeys, children[i].getPtr())) - return true; - } - } - } - return false; -} - -}; -NIFSkeletonLoader::LoaderMap NIFSkeletonLoader::sLoaders; - - -// Conversion of blend / test mode from NIF -> OGRE. -// Not in use yet, so let's comment it out. -/* -static SceneBlendFactor getBlendFactor(int mode) -{ - switch(mode) - { - case 0: return SBF_ONE; - case 1: return SBF_ZERO; - case 2: return SBF_SOURCE_COLOUR; - case 3: return SBF_ONE_MINUS_SOURCE_COLOUR; - case 4: return SBF_DEST_COLOUR; - case 5: return SBF_ONE_MINUS_DEST_COLOUR; - case 6: return SBF_SOURCE_ALPHA; - case 7: return SBF_ONE_MINUS_SOURCE_ALPHA; - case 8: return SBF_DEST_ALPHA; - case 9: return SBF_ONE_MINUS_DEST_ALPHA; - // [Comment from Chris Robinson:] Can't handle this mode? :/ - // case 10: return SBF_SOURCE_ALPHA_SATURATE; - default: - return SBF_SOURCE_ALPHA; - } -} - - -// This is also unused -static CompareFunction getTestMode(int mode) -{ - switch(mode) - { - case 0: return CMPF_ALWAYS_PASS; - case 1: return CMPF_LESS; - case 2: return CMPF_EQUAL; - case 3: return CMPF_LESS_EQUAL; - case 4: return CMPF_GREATER; - case 5: return CMPF_NOT_EQUAL; - case 6: return CMPF_GREATER_EQUAL; - case 7: return CMPF_ALWAYS_FAIL; - default: - return CMPF_ALWAYS_PASS; - } -} -*/ - - -class NIFMaterialLoader { - -static std::map MaterialMap; - -static void warn(const std::string &msg) -{ - std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; -} - -static void fail(const std::string &msg) -{ - std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; - abort(); -} - - -public: -static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &name, const Ogre::String &group) -{ - Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); - Ogre::MaterialPtr material = matMgr.getByName(name); - if(!material.isNull()) - return name; - - Ogre::Vector3 ambient(1.0f); - Ogre::Vector3 diffuse(1.0f); - Ogre::Vector3 specular(0.0f); - Ogre::Vector3 emissive(0.0f); - float glossiness = 0.0f; - float alpha = 1.0f; - int alphaFlags = -1; -// ubyte alphaTest = 0; - Ogre::String texName; - - bool vertexColour = (shape->data->colors.size() != 0); - - // These are set below if present - const NiTexturingProperty *t = NULL; - const NiMaterialProperty *m = NULL; - const NiAlphaProperty *a = NULL; - - // Scan the property list for material information - const PropertyList &list = shape->props; - for (size_t i = 0;i < list.length();i++) - { - // Entries may be empty - if (list[i].empty()) continue; - - const Property *pr = list[i].getPtr(); - if (pr->recType == RC_NiTexturingProperty) - t = static_cast(pr); - else if (pr->recType == RC_NiMaterialProperty) - m = static_cast(pr); - else if (pr->recType == RC_NiAlphaProperty) - a = static_cast(pr); - else - warn("Skipped property type: "+pr->recName); - } - - // Texture - if (t && t->textures[0].inUse) - { - NiSourceTexture *st = t->textures[0].texture.getPtr(); - if (st->external) - { - /* Bethesda at some at some point converted all their BSA - * textures from tga to dds for increased load speed, but all - * texture file name references were kept as .tga. - */ - texName = "textures\\" + st->filename; - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) - { - Ogre::String::size_type pos = texName.rfind('.'); - texName.replace(pos, texName.length(), ".dds"); - } - } - else warn("Found internal texture, ignoring."); - } - - // Alpha modifiers - if (a) - { - alphaFlags = a->flags; -// alphaTest = a->data.threshold; - } - - // Material - if(m) - { - ambient = m->data.ambient; - diffuse = m->data.diffuse; - specular = m->data.specular; - emissive = m->data.emissive; - glossiness = m->data.glossiness; - alpha = m->data.alpha; - } - - Ogre::String matname = name; - if (m || !texName.empty()) - { - // Generate a hash out of all properties that can affect the material. - size_t h = 0; - boost::hash_combine(h, ambient.x); - boost::hash_combine(h, ambient.y); - boost::hash_combine(h, ambient.z); - boost::hash_combine(h, diffuse.x); - boost::hash_combine(h, diffuse.y); - boost::hash_combine(h, diffuse.z); - boost::hash_combine(h, specular.x); - boost::hash_combine(h, specular.y); - boost::hash_combine(h, specular.z); - boost::hash_combine(h, emissive.x); - boost::hash_combine(h, emissive.y); - boost::hash_combine(h, emissive.z); - boost::hash_combine(h, texName); - boost::hash_combine(h, vertexColour); - boost::hash_combine(h, alphaFlags); - - std::map::iterator itr = MaterialMap.find(h); - if (itr != MaterialMap.end()) - { - // a suitable material exists already - use it - return itr->second; - } - // not found, create a new one - MaterialMap.insert(std::make_pair(h, matname)); - } - - // No existing material like this. Create a new one. - sh::MaterialInstance* instance = sh::Factory::getInstance ().createMaterialInstance (matname, "openmw_objects_base"); - instance->setProperty ("ambient", sh::makeProperty ( - new sh::Vector3(ambient.x, ambient.y, ambient.z))); - - instance->setProperty ("diffuse", sh::makeProperty ( - new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); - - instance->setProperty ("specular", sh::makeProperty ( - new sh::Vector4(specular.x, specular.y, specular.z, glossiness))); - - instance->setProperty ("emissive", sh::makeProperty ( - new sh::Vector3(emissive.x, emissive.y, emissive.z))); - - instance->setProperty ("diffuseMap", sh::makeProperty(texName)); - - if (vertexColour) - instance->setProperty ("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); - - // Add transparency if NiAlphaProperty was present - if (alphaFlags != -1) - { - // The 237 alpha flags are by far the most common. Check - // NiAlphaProperty in nif/property.h if you need to decode - // other values. 237 basically means normal transparencly. - if (alphaFlags == 237) - { - NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); - if (result.first) - { - instance->setProperty("alpha_rejection", - sh::makeProperty(new sh::StringValue("greater_equal " + boost::lexical_cast(result.second)))); - } - else - { - // Enable transparency - instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); - instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue("off"))); - } - } - else - warn("Unhandled alpha setting for texture " + texName); - } - else - instance->getMaterial ()->setShadowCasterMaterial ("openmw_shadowcaster_noalpha"); - - // As of yet UNTESTED code from Chris: - /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); - pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); - pass->setDepthCheckEnabled(true); - - // Add transparency if NiAlphaProperty was present - if (alphaFlags != -1) - { - std::cout << "Alpha flags set!" << endl; - if ((alphaFlags&1)) - { - pass->setDepthWriteEnabled(false); - pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf), - getBlendFactor((alphaFlags>>5)&0xf)); - } - else - pass->setDepthWriteEnabled(true); - - if ((alphaFlags>>9)&1) - pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7), - alphaTest); - - pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1)); - } -*/ - - return matname; -} - -}; -std::map NIFMaterialLoader::MaterialMap; - - -class NIFMeshLoader : Ogre::ManualResourceLoader -{ - std::string mName; - std::string mGroup; - std::string mShapeName; - std::string mMaterialName; - std::string mSkelName; - - void warn(const std::string &msg) - { - std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; - } - - void fail(const std::string &msg) - { - std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; - abort(); - } - - - // Convert NiTriShape to Ogre::SubMesh - void handleNiTriShape(Ogre::Mesh *mesh, Nif::NiTriShape *shape) - { - Ogre::SkeletonPtr skel; - const Nif::NiTriShapeData *data = shape->data.getPtr(); - const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr()); - std::vector srcVerts = data->vertices; - std::vector srcNorms = data->normals; - if(skin != NULL) - { - // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be - // explicitly attached later. - mesh->setSkeletonName(mSkelName); - - // Get the skeleton resource, so vertices can be transformed into the bones' initial state. - Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); - skel = skelMgr->getByName(mSkelName); - skel->touch(); - - // Convert vertices and normals to bone space from bind position. It would be - // better to transform the bones into bind position, but there doesn't seem to - // be a reliable way to do that. - std::vector newVerts(srcVerts.size(), Ogre::Vector3(0.0f)); - std::vector newNorms(srcNorms.size(), Ogre::Vector3(0.0f)); - - const Nif::NiSkinData *data = skin->data.getPtr(); - const Nif::NodeList &bones = skin->bones; - for(size_t b = 0;b < bones.length();b++) - { - Ogre::Bone *bone = skel->getBone(bones[b]->name); - Ogre::Matrix4 mat, mat2; - mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), - Ogre::Quaternion(data->bones[b].trafo.rotation)); - mat2.makeTransform(bone->_getDerivedPosition(), bone->_getDerivedScale(), - bone->_getDerivedOrientation()); - mat = mat2 * mat; - - const std::vector &weights = data->bones[b].weights; - for(size_t i = 0;i < weights.size();i++) - { - size_t index = weights[i].vertex; - float weight = weights[i].weight; - - newVerts.at(index) += (mat*srcVerts[index]) * weight; - if(newNorms.size() > index) - { - Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f); - vec4 = mat*vec4 * weight; - newNorms[index] += Ogre::Vector3(&vec4[0]); - } - } - } - - srcVerts = newVerts; - srcNorms = newNorms; - } - else if(mSkelName.length() == 0) - { - // No skinning and no skeleton, so just transform the vertices and - // normals into position. - Ogre::Matrix4 mat4 = shape->getWorldTransform(); - for(size_t i = 0;i < srcVerts.size();i++) - { - Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f); - vec4 = mat4*vec4; - srcVerts[i] = Ogre::Vector3(&vec4[0]); - } - for(size_t i = 0;i < srcNorms.size();i++) - { - Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f); - vec4 = mat4*vec4; - srcNorms[i] = Ogre::Vector3(&vec4[0]); - } - } - - // Set the bounding box first - BoundsFinder bounds; - bounds.add(&srcVerts[0][0], srcVerts.size()); - // No idea why this offset is needed. It works fine without it if the - // vertices weren't transformed first, but otherwise it fails later on - // when the object is being inserted into the scene. - mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f, - bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f)); - mesh->_setBoundingSphereRadius(bounds.getRadius()); - - // This function is just one long stream of Ogre-barf, but it works - // great. - Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareVertexBufferSharedPtr vbuf; - Ogre::HardwareIndexBufferSharedPtr ibuf; - Ogre::VertexBufferBinding *bind; - Ogre::VertexDeclaration *decl; - int nextBuf = 0; - - Ogre::SubMesh *sub = mesh->createSubMesh(shape->name); - - // Add vertices - sub->useSharedVertices = false; - sub->vertexData = new Ogre::VertexData(); - sub->vertexData->vertexStart = 0; - sub->vertexData->vertexCount = srcVerts.size(); - - decl = sub->vertexData->vertexDeclaration; - bind = sub->vertexData->vertexBufferBinding; - if(srcVerts.size()) - { - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - srcVerts.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, - true); - vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true); - - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - bind->setBinding(nextBuf++, vbuf); - } - - // Vertex normals - if(srcNorms.size()) - { - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - srcNorms.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, - true); - vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true); - - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - bind->setBinding(nextBuf++, vbuf); - } - - // Vertex colors - const std::vector &colors = data->colors; - if(colors.size()) - { - Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); - std::vector colorsRGB(colors.size()); - for(size_t i = 0;i < colorsRGB.size();i++) - { - Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]); - rs->convertColourValue(clr, &colorsRGB[i]); - } - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), - colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, - true); - vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true); - decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - bind->setBinding(nextBuf++, vbuf); - } - - // Texture UV coordinates - size_t numUVs = data->uvlist.size(); - if(numUVs) - { - size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); - vbuf = hwBufMgr->createVertexBuffer(elemSize, srcVerts.size()*numUVs, - Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); - for(size_t i = 0;i < numUVs;i++) - { - const std::vector &uvlist = data->uvlist[i]; - vbuf->writeData(i*srcVerts.size()*elemSize, elemSize*srcVerts.size(), &uvlist[0], true); - decl->addElement(nextBuf, i*srcVerts.size()*elemSize, Ogre::VET_FLOAT2, - Ogre::VES_TEXTURE_COORDINATES, i); - } - bind->setBinding(nextBuf++, vbuf); - } - - // Triangle faces - const std::vector &srcIdx = data->triangles; - if(srcIdx.size()) - { - ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(), - Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); - ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true); - sub->indexData->indexBuffer = ibuf; - sub->indexData->indexCount = srcIdx.size(); - sub->indexData->indexStart = 0; - } - - // Assign bone weights for this TriShape - if(skin != NULL) - { - const Nif::NiSkinData *data = skin->data.getPtr(); - const Nif::NodeList &bones = skin->bones; - for(size_t i = 0;i < bones.length();i++) - { - Ogre::VertexBoneAssignment boneInf; - boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle(); - - const std::vector &weights = data->bones[i].weights; - for(size_t j = 0;j < weights.size();j++) - { - boneInf.vertexIndex = weights[j].vertex; - boneInf.weight = weights[j].weight; - sub->addBoneAssignment(boneInf); - } - } - } - - if(mMaterialName.length() > 0) - sub->setMaterialName(mMaterialName); - } - - bool findTriShape(Ogre::Mesh *mesh, Nif::Node *node) - { - if(node->recType == Nif::RC_NiTriShape && mShapeName == node->name) - { - handleNiTriShape(mesh, dynamic_cast(node)); - return true; - } - - Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - { - if(findTriShape(mesh, children[i].getPtr())) - return true; - } - } - } - return false; - } - - - typedef std::map LoaderMap; - static LoaderMap sLoaders; - -public: - NIFMeshLoader() - { } - NIFMeshLoader(const std::string &name, const std::string &group, const std::string skelName) - : mName(name), mGroup(group), mSkelName(skelName) - { } - - virtual void loadResource(Ogre::Resource *resource) - { - Ogre::Mesh *mesh = dynamic_cast(resource); - assert(mesh && "Attempting to load a mesh into a non-mesh resource!"); - - if(!mShapeName.length()) - { - if(mSkelName.length() > 0) - mesh->setSkeletonName(mSkelName); - return; - } - - Nif::NIFFile nif(mName); - Nif::Node *node = dynamic_cast(nif.getRecord(0)); - findTriShape(mesh, node); - } - - void createMeshes(const Nif::Node *node, MeshPairList &meshes, int flags=0) - { - flags |= node->flags; - - // Marker objects: just skip the entire node - /// \todo don't do this in the editor - if (node->name.find("marker") != std::string::npos) - return; - - Nif::ExtraPtr e = node->extra; - while(!e.empty()) - { - Nif::NiStringExtraData *sd; - Nif::NiTextKeyExtraData *td; - if((sd=dynamic_cast(e.getPtr())) != NULL) - { - // String markers may contain important information - // affecting the entire subtree of this obj - if(sd->string == "MRK") - { - // Marker objects. These are only visible in the - // editor. - flags |= 0x01; - } - } - else if((td=dynamic_cast(e.getPtr())) != NULL) - { - // TODO: Read and store text keys somewhere - } - else - warn("Unhandled extra data type "+e->recName); - e = e->extra; - } - - if(node->recType == Nif::RC_NiTriShape) - { - const NiTriShape *shape = dynamic_cast(node); - - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - std::string fullname = mName+"@shape="+shape->name; - if(mSkelName.length() > 0 && mName != mSkelName) - fullname += "@skel="+mSkelName; - - std::transform(fullname.begin(), fullname.end(), fullname.begin(), ::tolower); - Ogre::MeshPtr mesh = meshMgr.getByName(fullname); - if(mesh.isNull()) - { - NIFMeshLoader *loader = &sLoaders[fullname]; - *loader = *this; - if(!(flags&0x01)) // Not hidden - { - loader->mShapeName = shape->name; - loader->mMaterialName = NIFMaterialLoader::getMaterial(shape, fullname, mGroup); - } - - mesh = meshMgr.createManual(fullname, mGroup, loader); - mesh->setAutoBuildEdgeLists(false); - } - - meshes.push_back(std::make_pair(mesh, shape->name)); - } - else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && - node->recType != Nif::RC_NiRotatingParticles) - warn("Unhandled mesh node type: "+node->recName); - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - createMeshes(children[i].getPtr(), meshes, flags); - } - } - } -}; -NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; - - -MeshPairList NIFLoader::load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group) -{ - MeshPairList meshes; - - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - std::transform(skelName.begin(), skelName.end(), skelName.begin(), ::tolower); - - Nif::NIFFile nif(name); - if (nif.numRecords() < 1) - { - nif.warn("Found no records in NIF."); - return meshes; - } - - // The first record is assumed to be the root node - Nif::Record *r = nif.getRecord(0); - assert(r != NULL); - - Nif::Node *node = dynamic_cast(r); - if(node == NULL) - { - nif.warn("First record in file was not a node, but a "+ - r->recName+". Skipping file."); - return meshes; - } - - NIFSkeletonLoader skelldr; - bool hasSkel = skelldr.createSkeleton(skelName, group, textkeys, node); - - NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); - meshldr.createMeshes(node, meshes); - - return meshes; -} - -EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, TextKeyMap *textkeys, const std::string &name, const std::string &group) -{ - EntityList entitylist; - - MeshPairList meshes = load(name, name, textkeys, group); - if(meshes.size() == 0) - return entitylist; - - Ogre::SceneManager *sceneMgr = parent->getCreator(); - for(size_t i = 0;i < meshes.size();i++) - { - entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first->getName())); - Ogre::Entity *entity = entitylist.mEntities.back(); - if(!entitylist.mSkelBase && entity->hasSkeleton()) - entitylist.mSkelBase = entity; - } - - if(entitylist.mSkelBase) - { - parent->attachObject(entitylist.mSkelBase); - for(size_t i = 0;i < entitylist.mEntities.size();i++) - { - Ogre::Entity *entity = entitylist.mEntities[i]; - if(entity != entitylist.mSkelBase && entity->hasSkeleton()) - { - entity->shareSkeletonInstanceWith(entitylist.mSkelBase); - parent->attachObject(entity); - } - else if(entity != entitylist.mSkelBase) - entitylist.mSkelBase->attachObjectToBone(meshes[i].second, entity); - } - } - else - { - for(size_t i = 0;i < entitylist.mEntities.size();i++) - parent->attachObject(entitylist.mEntities[i]); - } - - return entitylist; -} - -struct checklow { - bool operator()(const char &a, const char &b) const - { - return ::tolower(a) == ::tolower(b); - } -}; - -EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, - Ogre::SceneNode *parentNode, - const std::string &name, - const std::string &group) -{ - EntityList entitylist; - - MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), NULL, group); - if(meshes.size() == 0) - return entitylist; - - Ogre::SceneManager *sceneMgr = parentNode->getCreator(); - std::string filter = "Tri "+bonename; - for(size_t i = 0;i < meshes.size();i++) - { - Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].first->getName()); - if(ent->hasSkeleton()) - { - if(meshes[i].second.length() < filter.length() || - std::mismatch(filter.begin(), filter.end(), meshes[i].second.begin(), checklow()).first != filter.end()) - { - sceneMgr->destroyEntity(ent); - meshes.erase(meshes.begin()+i); - i--; - continue; - } - if(!entitylist.mSkelBase) - entitylist.mSkelBase = ent; - } - entitylist.mEntities.push_back(ent); - } - - Ogre::Vector3 scale(1.0f); - if(bonename.find("Left") != std::string::npos) - scale.x *= -1.0f; - - if(entitylist.mSkelBase) - { - entitylist.mSkelBase->shareSkeletonInstanceWith(parent); - parentNode->attachObject(entitylist.mSkelBase); - for(size_t i = 0;i < entitylist.mEntities.size();i++) - { - Ogre::Entity *entity = entitylist.mEntities[i]; - if(entity != entitylist.mSkelBase && entity->hasSkeleton()) - { - entity->shareSkeletonInstanceWith(parent); - parentNode->attachObject(entity); - } - else if(entity != entitylist.mSkelBase) - { - Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); - tag->setScale(scale); - } - } - } - else - { - for(size_t i = 0;i < entitylist.mEntities.size();i++) - { - Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entitylist.mEntities[i]); - tag->setScale(scale); - } - } - - return entitylist; -} - - -/* More code currently not in use, from the old D source. This was - used in the first attempt at loading NIF meshes, where each submesh - in the file was given a separate bone in a skeleton. Unfortunately - the OGRE skeletons can't hold more than 256 bones, and some NIFs go - way beyond that. The code might be of use if we implement animated - submeshes like this (the part of the NIF that is animated is - usually much less than the entire file, but the method might still - not be water tight.) - -// Insert a raw RGBA image into the texture system. -extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, void *data) -{ - TexturePtr texture = TextureManager::getSingleton().createManual( - name, // name - "General", // group - TEX_TYPE_2D, // type - width, height, // width & height - 0, // number of mipmaps - PF_BYTE_RGBA, // pixel format - TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for - // textures updated very often (e.g. each frame) - - // Get the pixel buffer - HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer(); - - // Lock the pixel buffer and get a pixel box - pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD! - const PixelBox& pixelBox = pixelBuffer->getCurrentLock(); - - void *dest = pixelBox.data; - - // Copy the data - memcpy(dest, data, width*height*4); - - // Unlock the pixel buffer - pixelBuffer->unlock(); -} - - -*/ diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp deleted file mode 100644 index a203112b5..000000000 --- a/components/nifogre/ogre_nif_loader.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (ogre_nif_loader.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#ifndef _OGRE_NIF_LOADER_H_ -#define _OGRE_NIF_LOADER_H_ - -#include -#include -#include - -#include -#include -#include -#include - -#include "../nif/node.hpp" - -#include - -class BoundsFinder; - -struct ciLessBoost : std::binary_function -{ - bool operator() (const std::string & s1, const std::string & s2) const - { - //case insensitive version of is_less - return boost::algorithm::lexicographical_compare(s1, s2, boost::algorithm::is_iless()); - } -}; - -namespace Nif -{ - class Node; - class Transformation; - class NiTriShape; -} - -namespace NifOgre -{ - -// FIXME: These should not be in NifOgre, it works agnostic of what model format is used -typedef std::multimap TextKeyMap; -struct EntityList { - std::vector mEntities; - Ogre::Entity *mSkelBase; - - EntityList() : mSkelBase(0) - { } -}; - - -/** This holds a list of meshes along with the names of their parent nodes - */ -typedef std::vector< std::pair > MeshPairList; - -/** Manual resource loader for NIF meshes. This is the main class - responsible for translating the internal NIF mesh structure into - something Ogre can use. - - You have to insert meshes manually into Ogre like this: - - NIFLoader::load("somemesh.nif"); - - This returns a list of meshes used by the model, as well as the names of - their parent nodes (as they pertain to the skeleton, which is optionally - returned in the second argument if it exists). - */ -class NIFLoader -{ - static MeshPairList load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group); - -public: - static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, - Ogre::SceneNode *parentNode, - const std::string &name, - const std::string &group="General"); - - static EntityList createEntities(Ogre::SceneNode *parent, - TextKeyMap *textkeys, - const std::string &name, - const std::string &group="General"); -}; - -} - -#endif - - diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp new file mode 100644 index 000000000..30c71023b --- /dev/null +++ b/components/nifogre/ogrenifloader.cpp @@ -0,0 +1,1404 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (ogre_nif_loader.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + */ + +#include "ogrenifloader.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +typedef unsigned char ubyte; + +namespace std +{ + +// TODO: Do something useful +ostream& operator<<(ostream &o, const NifOgre::TextKeyMap&) +{ return o; } + +} + +namespace NifOgre +{ +// Helper class that computes the bounding box and of a mesh +class BoundsFinder +{ + struct MaxMinFinder + { + float max, min; + + MaxMinFinder() + { + min = std::numeric_limits::infinity(); + max = -min; + } + + void add(float f) + { + if (f > max) max = f; + if (f < min) min = f; + } + + // Return Max(max**2, min**2) + float getMaxSquared() + { + float m1 = max*max; + float m2 = min*min; + if (m1 >= m2) return m1; + return m2; + } + }; + + MaxMinFinder X, Y, Z; + +public: + // Add 'verts' vertices to the calculation. The 'data' pointer is + // expected to point to 3*verts floats representing x,y,z for each + // point. + void add(float *data, int verts) + { + for (int i=0;i &ctrls, const std::vector &targets, float startTime, float stopTime) +{ + Ogre::Animation *anim = skel->createAnimation(name, stopTime-startTime); + + for(size_t i = 0;i < ctrls.size();i++) + { + const Nif::NiKeyframeController *kfc = ctrls[i]; + if(kfc->data.empty()) + continue; + const Nif::NiKeyframeData *kf = kfc->data.getPtr(); + + /* Get the keyframes and make sure they're sorted first to last */ + const Nif::QuaternionKeyList &quatkeys = kf->mRotations; + const Nif::Vector3KeyList &trankeys = kf->mTranslations; + const Nif::FloatKeyList &scalekeys = kf->mScales; + + Nif::QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin(); + Nif::Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin(); + Nif::FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin(); + + Ogre::Bone *bone = skel->getBone(targets[i]); + // NOTE: For some reason, Ogre doesn't like the node track ID being different from + // the bone ID + Ogre::NodeAnimationTrack *nodetrack = anim->hasNodeTrack(bone->getHandle()) ? + anim->getNodeTrack(bone->getHandle()) : + anim->createNodeTrack(bone->getHandle(), bone); + + Ogre::Quaternion lastquat, curquat; + Ogre::Vector3 lasttrans(0.0f), curtrans(0.0f); + Ogre::Vector3 lastscale(1.0f), curscale(1.0f); + if(quatiter != quatkeys.mKeys.end()) + lastquat = curquat = quatiter->mValue; + if(traniter != trankeys.mKeys.end()) + lasttrans = curtrans = traniter->mValue; + if(scaleiter != scalekeys.mKeys.end()) + lastscale = curscale = Ogre::Vector3(scaleiter->mValue); + float begTime = std::max(kfc->timeStart, startTime); + float endTime = std::min(kfc->timeStop, stopTime); + bool didlast = false; + + while(!didlast) + { + float curtime = std::numeric_limits::max(); + + //Get latest time + if(quatiter != quatkeys.mKeys.end()) + curtime = std::min(curtime, quatiter->mTime); + if(traniter != trankeys.mKeys.end()) + curtime = std::min(curtime, traniter->mTime); + if(scaleiter != scalekeys.mKeys.end()) + curtime = std::min(curtime, scaleiter->mTime); + + curtime = std::max(curtime, begTime); + if(curtime >= endTime) + { + didlast = true; + curtime = endTime; + } + + // Get the latest quaternions, translations, and scales for the + // current time + while(quatiter != quatkeys.mKeys.end() && curtime >= quatiter->mTime) + { + lastquat = curquat; + if(++quatiter != quatkeys.mKeys.end()) + curquat = quatiter->mValue; + } + while(traniter != trankeys.mKeys.end() && curtime >= traniter->mTime) + { + lasttrans = curtrans; + if(++traniter != trankeys.mKeys.end()) + curtrans = traniter->mValue; + } + while(scaleiter != scalekeys.mKeys.end() && curtime >= scaleiter->mTime) + { + lastscale = curscale; + if(++scaleiter != scalekeys.mKeys.end()) + curscale = Ogre::Vector3(scaleiter->mValue); + } + + Ogre::TransformKeyFrame *kframe; + kframe = nodetrack->createNodeKeyFrame(curtime-startTime); + if(quatiter == quatkeys.mKeys.end() || quatiter == quatkeys.mKeys.begin()) + kframe->setRotation(curquat); + else + { + Nif::QuaternionKeyList::VecType::const_iterator last = quatiter-1; + float diff = (curtime-last->mTime) / (quatiter->mTime-last->mTime); + kframe->setRotation(Ogre::Quaternion::nlerp(diff, lastquat, curquat)); + } + if(traniter == trankeys.mKeys.end() || traniter == trankeys.mKeys.begin()) + kframe->setTranslate(curtrans); + else + { + Nif::Vector3KeyList::VecType::const_iterator last = traniter-1; + float diff = (curtime-last->mTime) / (traniter->mTime-last->mTime); + kframe->setTranslate(lasttrans + ((curtrans-lasttrans)*diff)); + } + if(scaleiter == scalekeys.mKeys.end() || scaleiter == scalekeys.mKeys.begin()) + kframe->setScale(curscale); + else + { + Nif::FloatKeyList::VecType::const_iterator last = scaleiter-1; + float diff = (curtime-last->mTime) / (scaleiter->mTime-last->mTime); + kframe->setScale(lastscale + ((curscale-lastscale)*diff)); + } + } + } + anim->optimise(); +} + + +static TextKeyMap extractTextKeys(const Nif::NiTextKeyExtraData *tk) +{ + TextKeyMap textkeys; + for(size_t i = 0;i < tk->list.size();i++) + { + const std::string &str = tk->list[i].text; + std::string::size_type pos = 0; + while(pos < str.length()) + { + if(::isspace(str[pos])) + { + pos++; + continue; + } + + std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); + std::string result = str.substr(pos, nextpos-pos); + textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); + + pos = nextpos; + } + } + return textkeys; +} + + +void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *&animroot, TextKeyMap &textkeys, std::vector &ctrls, Ogre::Bone *parent=NULL) +{ + Ogre::Bone *bone; + if(!skel->hasBone(node->name)) + bone = skel->createBone(node->name); + else + bone = skel->createBone(); + if(parent) parent->addChild(bone); + + bone->setOrientation(node->trafo.rotation); + bone->setPosition(node->trafo.pos); + bone->setScale(Ogre::Vector3(node->trafo.scale)); + bone->setBindingPose(); + + if(!(node->recType == Nif::RC_NiNode || /* Nothing special; children traversed below */ + node->recType == Nif::RC_RootCollisionNode || /* handled in nifbullet (hopefully) */ + node->recType == Nif::RC_NiTriShape /* Handled in the mesh loader */ + )) + warn("Unhandled "+node->recName+" "+node->name+" in "+skel->getName()); + + Nif::ControllerPtr ctrl = node->controller; + while(!ctrl.empty()) + { + if(ctrl->recType == Nif::RC_NiKeyframeController) + ctrls.push_back(static_cast(ctrl.getPtr())); + else + warn("Unhandled "+ctrl->recName+" from node "+node->name+" in "+skel->getName()); + ctrl = ctrl->next; + } + + Nif::ExtraPtr e = node->extra; + while(!e.empty()) + { + if(e->recType == Nif::RC_NiTextKeyExtraData && !animroot) + { + const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); + textkeys = extractTextKeys(tk); + animroot = bone; + } + e = e->extra; + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + buildBones(skel, children[i].getPtr(), animroot, textkeys, ctrls, bone); + } + } +} + + +typedef std::map LoaderMap; +static LoaderMap sLoaders; + +public: +void loadResource(Ogre::Resource *resource) +{ + Ogre::Skeleton *skel = dynamic_cast(resource); + OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); + + Nif::NIFFile::ptr nif(Nif::NIFFile::create(skel->getName())); + const Nif::Node *node = static_cast(nif->getRecord(0)); + + std::vector ctrls; + Ogre::Bone *animroot = NULL; + TextKeyMap textkeys; + try { + buildBones(skel, node, animroot, textkeys, ctrls); + } + catch(std::exception &e) { + std::cerr<< "Exception while loading "<getName() < targets; + // TODO: If ctrls.size() == 0, check for a .kf file sharing the name of the .nif file + if(ctrls.size() == 0) // No animations? Then we're done. + return; + + float maxtime = 0.0f; + for(size_t i = 0;i < ctrls.size();i++) + { + const Nif::NiKeyframeController *ctrl = ctrls[i]; + maxtime = std::max(maxtime, ctrl->timeStop); + Nif::Named *target = dynamic_cast(ctrl->target.getPtr()); + if(target != NULL) + targets.push_back(target->name); + } + + if(targets.size() != ctrls.size()) + { + warn("Target size mismatch ("+Ogre::StringConverter::toString(targets.size())+" targets, "+ + Ogre::StringConverter::toString(ctrls.size())+" controllers)"); + return; + } + + if(!animroot) + { + warn(Ogre::StringConverter::toString(ctrls.size())+" animated node(s) in "+ + skel->getName()+", but no text keys."); + return; + } + + Ogre::UserObjectBindings &bindings = animroot->getUserObjectBindings(); + bindings.setUserAny(sTextKeyExtraDataID, Ogre::Any(true)); + + std::string currentgroup; + TextKeyMap::const_iterator keyiter = textkeys.begin(); + for(keyiter = textkeys.begin();keyiter != textkeys.end();keyiter++) + { + std::string::size_type sep = keyiter->second.find(':'); + if((sep == currentgroup.length() && keyiter->second.compare(0, sep, currentgroup) == 0) || + (sep == sizeof("soundgen")-1 && keyiter->second.compare(0, sep, "soundgen") == 0) || + (sep == sizeof("sound")-1 && keyiter->second.compare(0, sep, "sound") == 0)) + continue; + currentgroup = keyiter->second.substr(0, sep); + + if(skel->hasAnimation(currentgroup)) + continue; + + TextKeyMap::const_iterator lastkeyiter = textkeys.end(); + while((--lastkeyiter)->first > keyiter->first) + { + if(lastkeyiter->second.find(':') == currentgroup.length() && + lastkeyiter->second.compare(0, currentgroup.length(), currentgroup) == 0) + break; + } + + buildAnimation(skel, currentgroup, ctrls, targets, keyiter->first, lastkeyiter->first); + + TextKeyMap::const_iterator insiter = keyiter; + TextKeyMap groupkeys; + do { + sep = insiter->second.find(':'); + if(sep == currentgroup.length() && insiter->second.compare(0, sep, currentgroup) == 0) + groupkeys.insert(std::make_pair(insiter->first - keyiter->first, + insiter->second.substr(sep+2))); + else if((sep == sizeof("soundgen")-1 && insiter->second.compare(0, sep, "soundgen") == 0) || + (sep == sizeof("sound")-1 && insiter->second.compare(0, sep, "sound") == 0)) + groupkeys.insert(std::make_pair(insiter->first - keyiter->first, insiter->second)); + } while(insiter++ != lastkeyiter); + + bindings.setUserAny(std::string(sTextKeyExtraDataID)+"@"+currentgroup, Ogre::Any(groupkeys)); + } +} + + +static Ogre::SkeletonPtr createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node) +{ + /* We need to be a little aggressive here, since some NIFs have a crap-ton + * of nodes and Ogre only supports 256 bones. We will skip a skeleton if: + * There are no bones used for skinning, there are no controllers on non- + * NiTriShape nodes, there are no nodes named "AttachLight", and the tree + * consists of NiNode, NiTriShape, and RootCollisionNode types only. + */ + if(!node->boneTrafo) + { + if(node->recType == Nif::RC_NiTriShape) + return Ogre::SkeletonPtr(); + if(node->controller.empty() && node->name != "AttachLight") + { + if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode) + { + const Nif::NiNode *ninode = static_cast(node); + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + { + Ogre::SkeletonPtr skel = createSkeleton(name, group, children[i].getPtr()); + if(!skel.isNull()) + return skel; + } + } + return Ogre::SkeletonPtr(); + } + } + } + + Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); + return skelMgr.create(name, group, true, &sLoaders[name]); +} + +}; +NIFSkeletonLoader::LoaderMap NIFSkeletonLoader::sLoaders; + + +// Conversion of blend / test mode from NIF +static const char *getBlendFactor(int mode) +{ + switch(mode) + { + case 0: return "one"; + case 1: return "zero"; + case 2: return "src_colour"; + case 3: return "one_minus_src_colour"; + case 4: return "dest_colour"; + case 5: return "one_minus_dest_colour"; + case 6: return "src_alpha"; + case 7: return "one_minus_src_alpha"; + case 8: return "dest_alpha"; + case 9: return "one_minus_dest_alpha"; + case 10: return "src_alpha_saturate"; + } + std::cerr<< "Unexpected blend mode: "< MaterialMap; + +static void warn(const std::string &msg) +{ + std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; +} + +static void fail(const std::string &msg) +{ + std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; + abort(); +} + + +public: +static Ogre::String getMaterial(const Nif::NiTriShape *shape, const Ogre::String &name, const Ogre::String &group, + const Nif::NiTexturingProperty *texprop, + const Nif::NiMaterialProperty *matprop, + const Nif::NiAlphaProperty *alphaprop, + const Nif::NiVertexColorProperty *vertprop, + const Nif::NiZBufferProperty *zprop, + const Nif::NiSpecularProperty *specprop) +{ + Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); + Ogre::MaterialPtr material = matMgr.getByName(name); + if(!material.isNull()) + return name; + + Ogre::Vector3 ambient(1.0f); + Ogre::Vector3 diffuse(1.0f); + Ogre::Vector3 specular(0.0f); + Ogre::Vector3 emissive(0.0f); + float glossiness = 0.0f; + float alpha = 1.0f; + int alphaFlags = 0; + int alphaTest = 0; + int vertMode = 2; + //int lightMode = 1; + int depthFlags = 3; + // Default should be 1, but Bloodmoon's models are broken + int specFlags = 0; + Ogre::String texName; + + bool vertexColour = (shape->data->colors.size() != 0); + + // Texture + if(texprop && texprop->textures[0].inUse) + { + const Nif::NiSourceTexture *st = texprop->textures[0].texture.getPtr(); + if(st->external) + { + /* Bethesda at some point converted all their BSA + * textures from tga to dds for increased load speed, but all + * texture file name references were kept as .tga. + */ + static const char path[] = "textures\\"; + + texName = st->filename; + Misc::StringUtils::toLower(texName); + + if(texName.compare(0, sizeof(path)-1, path) != 0) + texName = path + texName; + + Ogre::String::size_type pos = texName.rfind('.'); + if(pos != Ogre::String::npos && texName.compare(pos, texName.length() - pos, ".dds") != 0) + { + // since we know all (GOTY edition or less) textures end + // in .dds, we change the extension + texName.replace(pos, texName.length(), ".dds"); + + // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) + // verify, and revert if false (this call succeeds quickly, but fails slowly) + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) + { + texName = st->filename; + Misc::StringUtils::toLower(texName); + if(texName.compare(0, sizeof(path)-1, path) != 0) + texName = path + texName; + } + } + } + else warn("Found internal texture, ignoring."); + } + + // Alpha modifiers + if(alphaprop) + { + alphaFlags = alphaprop->flags; + alphaTest = alphaprop->data.threshold; + } + + // Vertex color handling + if(vertprop) + { + vertMode = vertprop->data.vertmode; + // FIXME: Handle lightmode? + //lightMode = vertprop->data.lightmode; + } + + if(zprop) + { + depthFlags = zprop->flags; + // Depth function??? + } + + if(specprop) + { + specFlags = specprop->flags; + } + + // Material + if(matprop) + { + ambient = matprop->data.ambient; + diffuse = matprop->data.diffuse; + specular = matprop->data.specular; + emissive = matprop->data.emissive; + glossiness = matprop->data.glossiness; + alpha = matprop->data.alpha; + } + + Ogre::String matname = name; + if(matprop || !texName.empty()) + { + // Generate a hash out of all properties that can affect the material. + size_t h = 0; + boost::hash_combine(h, ambient.x); + boost::hash_combine(h, ambient.y); + boost::hash_combine(h, ambient.z); + boost::hash_combine(h, diffuse.x); + boost::hash_combine(h, diffuse.y); + boost::hash_combine(h, diffuse.z); + boost::hash_combine(h, specular.x); + boost::hash_combine(h, specular.y); + boost::hash_combine(h, specular.z); + boost::hash_combine(h, emissive.x); + boost::hash_combine(h, emissive.y); + boost::hash_combine(h, emissive.z); + boost::hash_combine(h, texName); + boost::hash_combine(h, vertexColour); + boost::hash_combine(h, alphaFlags); + boost::hash_combine(h, alphaTest); + boost::hash_combine(h, vertMode); + boost::hash_combine(h, depthFlags); + boost::hash_combine(h, specFlags); + + std::map::iterator itr = MaterialMap.find(h); + if (itr != MaterialMap.end()) + { + // a suitable material exists already - use it + return itr->second; + } + // not found, create a new one + MaterialMap.insert(std::make_pair(h, matname)); + } + + // No existing material like this. Create a new one. + sh::MaterialInstance* instance = sh::Factory::getInstance ().createMaterialInstance (matname, "openmw_objects_base"); + if(vertMode == 0 || !vertexColour) + { + instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1))); + instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); + instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1))); + instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("0"))); + } + else if(vertMode == 1) + { + instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1))); + instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); + instance->setProperty("emissive", sh::makeProperty(new sh::StringValue("vertexcolour"))); + instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("1"))); + } + else if(vertMode == 2) + { + instance->setProperty("ambient", sh::makeProperty(new sh::StringValue("vertexcolour"))); + instance->setProperty("diffuse", sh::makeProperty(new sh::StringValue("vertexcolour"))); + instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1))); + instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("2"))); + } + else + std::cerr<< "Unhandled vertex mode: "<setProperty("specular", sh::makeProperty( + new sh::Vector4(specular.x, specular.y, specular.z, glossiness))); + } + + instance->setProperty("diffuseMap", sh::makeProperty(texName)); + + if (vertexColour) + instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); + + // Add transparency if NiAlphaProperty was present + NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); + if (result.first) + { + alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ + alphaTest = result.second; + } + + if((alphaFlags&1)) + { + std::string blend_mode; + blend_mode += getBlendFactor((alphaFlags>>1)&0xf); + blend_mode += " "; + blend_mode += getBlendFactor((alphaFlags>>5)&0xf); + instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue(blend_mode))); + } + else + instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha"); + + if((alphaFlags>>9)&1) + { + std::string reject; + reject += getTestMode((alphaFlags>>10)&0x7); + reject += " "; + reject += Ogre::StringConverter::toString(alphaTest); + instance->setProperty("alpha_rejection", sh::makeProperty(new sh::StringValue(reject))); + } + + // Ogre usually only sorts if depth write is disabled, so we want "force" instead of "on" + instance->setProperty("transparent_sorting", sh::makeProperty(new sh::StringValue(!((alphaFlags>>13)&1) ? "force" : "off"))); + + instance->setProperty("depth_check", sh::makeProperty(new sh::StringValue((depthFlags&1) ? "on" : "off"))); + instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off"))); + // depth_func??? + + sh::Factory::getInstance()._ensureMaterial(matname, "Default"); + return matname; +} + +}; +std::map NIFMaterialLoader::MaterialMap; + + +/** Manual resource loader for NIF meshes. This is the main class + responsible for translating the internal NIF mesh structure into + something Ogre can use. + */ +class NIFMeshLoader : Ogre::ManualResourceLoader +{ + std::string mName; + std::string mGroup; + size_t mShapeIndex; + + void warn(const std::string &msg) + { + std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; + } + + void fail(const std::string &msg) + { + std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; + abort(); + } + + + // Convert NiTriShape to Ogre::SubMesh + void handleNiTriShape(Ogre::Mesh *mesh, Nif::NiTriShape const *shape, + const Nif::NiTexturingProperty *texprop, + const Nif::NiMaterialProperty *matprop, + const Nif::NiAlphaProperty *alphaprop, + const Nif::NiVertexColorProperty *vertprop, + const Nif::NiZBufferProperty *zprop, + const Nif::NiSpecularProperty *specprop) + { + Ogre::SkeletonPtr skel; + const Nif::NiTriShapeData *data = shape->data.getPtr(); + const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr()); + std::vector srcVerts = data->vertices; + std::vector srcNorms = data->normals; + if(skin != NULL) + { + // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be + // explicitly attached later. + mesh->setSkeletonName(mName); + + // Get the skeleton resource, so vertices can be transformed into the bones' initial state. + Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); + skel = skelMgr->getByName(mName); + + // Convert vertices and normals to bone space from bind position. It would be + // better to transform the bones into bind position, but there doesn't seem to + // be a reliable way to do that. + std::vector newVerts(srcVerts.size(), Ogre::Vector3(0.0f)); + std::vector newNorms(srcNorms.size(), Ogre::Vector3(0.0f)); + + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t b = 0;b < bones.length();b++) + { + Ogre::Bone *bone = skel->getBone(bones[b]->name); + Ogre::Matrix4 mat; + mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), + Ogre::Quaternion(data->bones[b].trafo.rotation)); + mat = bone->_getFullTransform() * mat; + + const std::vector &weights = data->bones[b].weights; + for(size_t i = 0;i < weights.size();i++) + { + size_t index = weights[i].vertex; + float weight = weights[i].weight; + + newVerts.at(index) += (mat*srcVerts[index]) * weight; + if(newNorms.size() > index) + { + Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f); + vec4 = mat*vec4 * weight; + newNorms[index] += Ogre::Vector3(&vec4[0]); + } + } + } + + srcVerts = newVerts; + srcNorms = newNorms; + } + else + { + Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); + if(skelMgr->getByName(mName).isNull()) + { + // No skinning and no skeleton, so just transform the vertices and + // normals into position. + Ogre::Matrix4 mat4 = shape->getWorldTransform(); + for(size_t i = 0;i < srcVerts.size();i++) + { + Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f); + vec4 = mat4*vec4; + srcVerts[i] = Ogre::Vector3(&vec4[0]); + } + for(size_t i = 0;i < srcNorms.size();i++) + { + Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f); + vec4 = mat4*vec4; + srcNorms[i] = Ogre::Vector3(&vec4[0]); + } + } + } + + // Set the bounding box first + BoundsFinder bounds; + bounds.add(&srcVerts[0][0], srcVerts.size()); + if(!bounds.isValid()) + { + float v[3] = { 0.0f, 0.0f, 0.0f }; + bounds.add(&v[0], 1); + } + + mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f, + bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f)); + mesh->_setBoundingSphereRadius(bounds.getRadius()); + + // This function is just one long stream of Ogre-barf, but it works + // great. + Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr vbuf; + Ogre::HardwareIndexBufferSharedPtr ibuf; + Ogre::VertexBufferBinding *bind; + Ogre::VertexDeclaration *decl; + int nextBuf = 0; + + Ogre::SubMesh *sub = mesh->createSubMesh(); + + // Add vertices + sub->useSharedVertices = false; + sub->vertexData = new Ogre::VertexData(); + sub->vertexData->vertexStart = 0; + sub->vertexData->vertexCount = srcVerts.size(); + + decl = sub->vertexData->vertexDeclaration; + bind = sub->vertexData->vertexBufferBinding; + if(srcVerts.size()) + { + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + srcVerts.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true); + + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); + bind->setBinding(nextBuf++, vbuf); + } + + // Vertex normals + if(srcNorms.size()) + { + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + srcNorms.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true); + + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); + bind->setBinding(nextBuf++, vbuf); + } + + // Vertex colors + const std::vector &colors = data->colors; + if(colors.size()) + { + Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); + std::vector colorsRGB(colors.size()); + for(size_t i = 0;i < colorsRGB.size();i++) + { + Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]); + rs->convertColourValue(clr, &colorsRGB[i]); + } + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true); + decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); + bind->setBinding(nextBuf++, vbuf); + } + + // Texture UV coordinates + size_t numUVs = data->uvlist.size(); + if(numUVs) + { + size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); + vbuf = hwBufMgr->createVertexBuffer(elemSize, srcVerts.size()*numUVs, + Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); + for(size_t i = 0;i < numUVs;i++) + { + const std::vector &uvlist = data->uvlist[i]; + vbuf->writeData(i*srcVerts.size()*elemSize, elemSize*srcVerts.size(), &uvlist[0], true); + decl->addElement(nextBuf, i*srcVerts.size()*elemSize, Ogre::VET_FLOAT2, + Ogre::VES_TEXTURE_COORDINATES, i); + } + bind->setBinding(nextBuf++, vbuf); + } + + // Triangle faces + const std::vector &srcIdx = data->triangles; + if(srcIdx.size()) + { + ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(), + Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); + ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true); + sub->indexData->indexBuffer = ibuf; + sub->indexData->indexCount = srcIdx.size(); + sub->indexData->indexStart = 0; + } + + // Assign bone weights for this TriShape + if(skin != NULL) + { + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t i = 0;i < bones.length();i++) + { + Ogre::VertexBoneAssignment boneInf; + boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle(); + + const std::vector &weights = data->bones[i].weights; + for(size_t j = 0;j < weights.size();j++) + { + boneInf.vertexIndex = weights[j].vertex; + boneInf.weight = weights[j].weight; + sub->addBoneAssignment(boneInf); + } + } + } + + std::string matname = NIFMaterialLoader::getMaterial(shape, mesh->getName(), mGroup, + texprop, matprop, alphaprop, + vertprop, zprop, specprop); + if(matname.length() > 0) + sub->setMaterialName(matname); + } + + bool findTriShape(Ogre::Mesh *mesh, const Nif::Node *node, + const Nif::NiTexturingProperty *texprop, + const Nif::NiMaterialProperty *matprop, + const Nif::NiAlphaProperty *alphaprop, + const Nif::NiVertexColorProperty *vertprop, + const Nif::NiZBufferProperty *zprop, + const Nif::NiSpecularProperty *specprop) + { + // Scan the property list for material information + const Nif::PropertyList &proplist = node->props; + for(size_t i = 0;i < proplist.length();i++) + { + // Entries may be empty + if(proplist[i].empty()) + continue; + + const Nif::Property *pr = proplist[i].getPtr(); + if(pr->recType == Nif::RC_NiTexturingProperty) + texprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiMaterialProperty) + matprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiAlphaProperty) + alphaprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiVertexColorProperty) + vertprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiZBufferProperty) + zprop = static_cast(pr); + else if(pr->recType == Nif::RC_NiSpecularProperty) + specprop = static_cast(pr); + else + warn("Unhandled property type: "+pr->recName); + } + + if(node->recType == Nif::RC_NiTriShape && mShapeIndex == node->recIndex) + { + handleNiTriShape(mesh, dynamic_cast(node), texprop, matprop, alphaprop, vertprop, zprop, specprop); + return true; + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + { + if(findTriShape(mesh, children[i].getPtr(), texprop, matprop, alphaprop, vertprop, zprop, specprop)) + return true; + } + } + } + return false; + } + + + typedef std::map LoaderMap; + static LoaderMap sLoaders; + +public: + NIFMeshLoader() + { } + NIFMeshLoader(const std::string &name, const std::string &group) + : mName(name), mGroup(group), mShapeIndex(~(size_t)0) + { } + + virtual void loadResource(Ogre::Resource *resource) + { + Ogre::Mesh *mesh = dynamic_cast(resource); + OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!"); + + Nif::NIFFile::ptr nif = Nif::NIFFile::create(mName); + if(mShapeIndex >= nif->numRecords()) + { + Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); + if(!skelMgr->getByName(mName).isNull()) + mesh->setSkeletonName(mName); + return; + } + + const Nif::Node *node = dynamic_cast(nif->getRecord(0)); + findTriShape(mesh, node, NULL, NULL, NULL, NULL, NULL, NULL); + } + + void createMeshes(const Nif::Node *node, MeshInfoList &meshes, int flags=0) + { + // Do not create meshes for the collision shape (includes all children) + if(node->recType == Nif::RC_RootCollisionNode) + return; + + flags |= node->flags; + + // Marker objects: just skip the entire node + /// \todo don't do this in the editor + if (node->name.find("marker") != std::string::npos) + return; + + Nif::ExtraPtr e = node->extra; + while(!e.empty()) + { + Nif::NiStringExtraData *sd; + if((sd=dynamic_cast(e.getPtr())) != NULL) + { + // String markers may contain important information + // affecting the entire subtree of this obj + if(sd->string == "MRK") + { + // Marker objects. These are only visible in the + // editor. + flags |= 0x01; + } + } + e = e->extra; + } + + if(node->recType == Nif::RC_NiTriShape && !(flags&0x01)) // Not hidden + { + const Nif::NiTriShape *shape = dynamic_cast(node); + + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); + std::string fullname = mName+"@index="+Ogre::StringConverter::toString(shape->recIndex); + if(shape->name.length() > 0) + fullname += "@shape="+shape->name; + + Misc::StringUtils::toLower(fullname); + Ogre::MeshPtr mesh = meshMgr.getByName(fullname); + if(mesh.isNull()) + { + NIFMeshLoader *loader = &sLoaders[fullname]; + *loader = *this; + loader->mShapeIndex = shape->recIndex; + + mesh = meshMgr.createManual(fullname, mGroup, loader); + mesh->setAutoBuildEdgeLists(false); + } + + meshes.push_back(MeshInfo(mesh->getName(), shape->name)); + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + createMeshes(children[i].getPtr(), meshes, flags); + } + } + } + + void createEmptyMesh(const Nif::Node *node, MeshInfoList &meshes) + { + /* This creates an empty mesh to which a skeleton gets attached. This + * is to ensure we have an entity with a skeleton instance, even if all + * other meshes are hidden or entities attached to a specific node + * instead of skinned. */ + std::string fullname = mName; + Misc::StringUtils::toLower(fullname); + + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); + Ogre::MeshPtr mesh = meshMgr.getByName(fullname); + if(mesh.isNull()) + { + NIFMeshLoader *loader = &sLoaders[fullname]; + *loader = *this; + + mesh = meshMgr.createManual(fullname, mGroup, loader); + mesh->setAutoBuildEdgeLists(false); + } + meshes.push_back(MeshInfo(mesh->getName(), node->name)); + } +}; +NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; + + +typedef std::map MeshInfoMap; +static MeshInfoMap sMeshInfoMap; + +MeshInfoList Loader::load(const std::string &name, const std::string &group) +{ + MeshInfoMap::const_iterator meshiter = sMeshInfoMap.find(name); + if(meshiter != sMeshInfoMap.end()) + return meshiter->second; + + MeshInfoList &meshes = sMeshInfoMap[name]; + Nif::NIFFile::ptr pnif = Nif::NIFFile::create(name); + Nif::NIFFile &nif = *pnif.get(); + if(nif.numRecords() < 1) + { + nif.warn("Found no NIF records in "+name+"."); + return meshes; + } + + // The first record is assumed to be the root node + Nif::Record const *r = nif.getRecord(0); + assert(r != NULL); + + Nif::Node const *node = dynamic_cast(r); + if(node == NULL) + { + nif.warn("First record in "+name+" was not a node, but a "+ + r->recName+"."); + return meshes; + } + + bool hasSkel = Ogre::SkeletonManager::getSingleton().resourceExists(name); + if(!hasSkel) + hasSkel = !NIFSkeletonLoader::createSkeleton(name, group, node).isNull(); + + NIFMeshLoader meshldr(name, group); + if(hasSkel) + meshldr.createEmptyMesh(node, meshes); + meshldr.createMeshes(node, meshes, 0); + + return meshes; +} + +EntityList Loader::createEntities(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +{ + EntityList entitylist; + + Misc::StringUtils::toLower(name); + MeshInfoList meshes = load(name, group); + if(meshes.size() == 0) + return entitylist; + + Ogre::SceneManager *sceneMgr = parentNode->getCreator(); + for(size_t i = 0;i < meshes.size();i++) + { + entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].mMeshName)); + Ogre::Entity *entity = entitylist.mEntities.back(); + if(!entitylist.mSkelBase && entity->hasSkeleton()) + entitylist.mSkelBase = entity; + } + + if(entitylist.mSkelBase) + { + parentNode->attachObject(entitylist.mSkelBase); + for(size_t i = 0;i < entitylist.mEntities.size();i++) + { + Ogre::Entity *entity = entitylist.mEntities[i]; + if(entity != entitylist.mSkelBase && entity->hasSkeleton()) + { + entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + parentNode->attachObject(entity); + } + else if(entity != entitylist.mSkelBase) + entitylist.mSkelBase->attachObjectToBone(meshes[i].mTargetNode, entity); + } + } + else + { + for(size_t i = 0;i < entitylist.mEntities.size();i++) + parentNode->attachObject(entitylist.mEntities[i]); + } + + return entitylist; +} + +EntityList Loader::createEntities(Ogre::Entity *parent, const std::string &bonename, + Ogre::SceneNode *parentNode, + std::string name, const std::string &group) +{ + EntityList entitylist; + + Misc::StringUtils::toLower(name); + MeshInfoList meshes = load(name, group); + if(meshes.size() == 0) + return entitylist; + + bool isskinned = false; + Ogre::SceneManager *sceneMgr = parentNode->getCreator(); + std::string filter = "@shape=tri "+bonename; + Misc::StringUtils::toLower(filter); + for(size_t i = 0;i < meshes.size();i++) + { + Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].mMeshName); + if(!entitylist.mSkelBase) + { + if(ent->hasSkeleton()) + entitylist.mSkelBase = ent; + } + else if(!isskinned && ent->hasSkeleton()) + isskinned = true; + entitylist.mEntities.push_back(ent); + } + + Ogre::Vector3 scale(1.0f); + if(bonename.find("Left") != std::string::npos) + scale.x *= -1.0f; + + if(isskinned) + { + for(size_t i = 0;i < entitylist.mEntities.size();i++) + { + Ogre::Entity *entity = entitylist.mEntities[i]; + if(entity->hasSkeleton()) + { + if(entity != entitylist.mSkelBase) + entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + if(entity->getMesh()->getName().find(filter) != std::string::npos) + parentNode->attachObject(entity); + } + else + { + if(entity->getMesh()->getName().find(filter) != std::string::npos) + entitylist.mSkelBase->attachObjectToBone(meshes[i].mTargetNode, entity); + } + } + } + else + { + for(size_t i = 0;i < entitylist.mEntities.size();i++) + { + Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entitylist.mEntities[i]); + tag->setScale(scale); + } + } + + return entitylist; +} + + +Ogre::SkeletonPtr Loader::getSkeleton(std::string name, const std::string &group) +{ + Ogre::SkeletonPtr skel; + + Misc::StringUtils::toLower(name); + skel = Ogre::SkeletonManager::getSingleton().getByName(name); + if(!skel.isNull()) + return skel; + + Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); + if(nif->numRecords() < 1) + { + nif->warn("Found no NIF records in "+name+"."); + return skel; + } + + // The first record is assumed to be the root node + const Nif::Record *r = nif->getRecord(0); + assert(r != NULL); + + const Nif::Node *node = dynamic_cast(r); + if(node == NULL) + { + nif->warn("First record in "+name+" was not a node, but a "+ + r->recName+"."); + return skel; + } + + return NIFSkeletonLoader::createSkeleton(name, group, node); +} + + +/* More code currently not in use, from the old D source. This was + used in the first attempt at loading NIF meshes, where each submesh + in the file was given a separate bone in a skeleton. Unfortunately + the OGRE skeletons can't hold more than 256 bones, and some NIFs go + way beyond that. The code might be of use if we implement animated + submeshes like this (the part of the NIF that is animated is + usually much less than the entire file, but the method might still + not be water tight.) + +// Insert a raw RGBA image into the texture system. +extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, void *data) +{ + TexturePtr texture = TextureManager::getSingleton().createManual( + name, // name + "General", // group + TEX_TYPE_2D, // type + width, height, // width & height + 0, // number of mipmaps + PF_BYTE_RGBA, // pixel format + TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for + // textures updated very often (e.g. each frame) + + // Get the pixel buffer + HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer(); + + // Lock the pixel buffer and get a pixel box + pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD! + const PixelBox& pixelBox = pixelBuffer->getCurrentLock(); + + void *dest = pixelBox.data; + + // Copy the data + memcpy(dest, data, width*height*4); + + // Unlock the pixel buffer + pixelBuffer->unlock(); +} + + +*/ + +} // nsmaepace NifOgre diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp new file mode 100644 index 000000000..b8b2e3c00 --- /dev/null +++ b/components/nifogre/ogrenifloader.hpp @@ -0,0 +1,92 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (ogre_nif_loader.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + */ + +#ifndef OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP +#define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP + +#include +#include +#include + +#include +#include + + +// FIXME: This namespace really doesn't do anything Nif-specific. Any supportable +// model format should go through this. +namespace NifOgre +{ + +typedef std::multimap TextKeyMap; +static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; +struct EntityList { + std::vector mEntities; + Ogre::Entity *mSkelBase; + + EntityList() : mSkelBase(0) + { } +}; + + +/* This holds a list of mesh names, the names of their parent nodes, and the offset + * from their parent nodes. */ +struct MeshInfo { + std::string mMeshName; + std::string mTargetNode; + + MeshInfo(const std::string &name, const std::string &target) + : mMeshName(name), mTargetNode(target) + { } +}; +typedef std::vector MeshInfoList; + +class Loader +{ + static MeshInfoList load(const std::string &name, const std::string &group); + +public: + static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, + Ogre::SceneNode *parentNode, + std::string name, + const std::string &group="General"); + + static EntityList createEntities(Ogre::SceneNode *parentNode, + std::string name, + const std::string &group="General"); + + static Ogre::SkeletonPtr getSkeleton(std::string name, const std::string &group="General"); +}; + +} + +namespace std +{ + +// These operators allow extra data types to be stored in an Ogre::Any +// object, which can then be stored in user object bindings on the nodes + +ostream& operator<<(ostream &o, const NifOgre::TextKeyMap&); + +} + +#endif diff --git a/components/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp index 1c8fefd24..191b4ac2f 100644 --- a/components/nifoverrides/nifoverrides.cpp +++ b/components/nifoverrides/nifoverrides.cpp @@ -2,7 +2,8 @@ #include -#include +#include <../components/misc/stringops.hpp> + using namespace NifOverrides; @@ -19,7 +20,7 @@ TransparencyResult Overrides::getTransparencyOverride(const std::string& texture result.first = false; std::string tex = texture; - boost::to_lower(tex); + Misc::StringUtils::toLower(tex); Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator(); while (seci.hasMoreElements()) diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp index c9b711df6..ba2e4cc3c 100644 --- a/components/nifoverrides/nifoverrides.hpp +++ b/components/nifoverrides/nifoverrides.hpp @@ -1,5 +1,5 @@ -#ifndef COMPONENTS_NIFOVERRIDES_H -#define COMPONENTS_NIFOVERRIDES_H +#ifndef OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP +#define OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP #include diff --git a/components/to_utf8/tables_gen.hpp b/components/to_utf8/tables_gen.hpp index 9c32f0427..a1d4b6d80 100644 --- a/components/to_utf8/tables_gen.hpp +++ b/components/to_utf8/tables_gen.hpp @@ -8,7 +8,7 @@ namespace ToUTF8 /// Central European and Eastern European languages that use Latin script, /// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, /// Serbian (Latin script), Romanian and Albanian. -static char windows_1250[] = +static signed char windows_1250[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, @@ -270,7 +270,7 @@ static char windows_1250[] = /// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic /// and other languages -static char windows_1251[] = +static signed char windows_1251[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, @@ -531,7 +531,7 @@ static char windows_1251[] = }; /// Latin alphabet used by English and some other Western languages -static char windows_1252[] = +static signed char windows_1252[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, diff --git a/components/to_utf8/tests/.gitignore b/components/to_utf8/tests/.gitignore new file mode 100644 index 000000000..814490404 --- /dev/null +++ b/components/to_utf8/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/components/to_utf8/tests/output/to_utf8_test.out b/components/to_utf8/tests/output/to_utf8_test.out new file mode 100644 index 000000000..dcb32359a --- /dev/null +++ b/components/to_utf8/tests/output/to_utf8_test.out @@ -0,0 +1,4 @@ +original: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? +converted: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? +original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. +converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. diff --git a/components/to_utf8/tests/test.sh b/components/to_utf8/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/components/to_utf8/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/components/to_utf8/tests/test_data/french-utf8.txt b/components/to_utf8/tests/test_data/french-utf8.txt new file mode 100644 index 000000000..aaaccac73 --- /dev/null +++ b/components/to_utf8/tests/test_data/french-utf8.txt @@ -0,0 +1 @@ +Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/french-win1252.txt b/components/to_utf8/tests/test_data/french-win1252.txt new file mode 100644 index 000000000..1de4593e9 --- /dev/null +++ b/components/to_utf8/tests/test_data/french-win1252.txt @@ -0,0 +1 @@ +Vous lui donnez le gteau sans protester avant daller chercher tous vos amis et de revenir vous venger. \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/russian-utf8.txt b/components/to_utf8/tests/test_data/russian-utf8.txt new file mode 100644 index 000000000..eb20b32dd --- /dev/null +++ b/components/to_utf8/tests/test_data/russian-utf8.txt @@ -0,0 +1 @@ +Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? \ No newline at end of file diff --git a/components/to_utf8/tests/test_data/russian-win1251.txt b/components/to_utf8/tests/test_data/russian-win1251.txt new file mode 100644 index 000000000..086e57edd --- /dev/null +++ b/components/to_utf8/tests/test_data/russian-win1251.txt @@ -0,0 +1 @@ + , , ? \ No newline at end of file diff --git a/components/to_utf8/tests/to_utf8_test.cpp b/components/to_utf8/tests/to_utf8_test.cpp new file mode 100644 index 000000000..3fcddd158 --- /dev/null +++ b/components/to_utf8/tests/to_utf8_test.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include "../to_utf8.hpp" + +std::string getFirstLine(const std::string &filename); +void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, + const std::string &utf8File); + +/// Test character encoding conversion to and from UTF-8 +void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, + const std::string &utf8File) +{ + // get some test data + std::string legacyEncLine = getFirstLine(legacyEncFile); + std::string utf8Line = getFirstLine(utf8File); + + // create an encoder for specified character encoding + ToUTF8::Utf8Encoder encoder (encoding); + + // convert text to UTF-8 + std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine); + + std::cout << "original: " << utf8Line << std::endl; + std::cout << "converted: " << convertedUtf8Line << std::endl; + + // check correctness + assert(convertedUtf8Line == utf8Line); + + // convert UTF-8 text to legacy encoding + std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line); + // check correctness + assert(convertedLegacyEncLine == legacyEncLine); +} + +std::string getFirstLine(const std::string &filename) +{ + std::string line; + std::ifstream text (filename.c_str()); + + if (!text.is_open()) + { + throw std::runtime_error("Unable to open file " + filename); + } + + std::getline(text, line); + text.close(); + + return line; +} + +int main() +{ + testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt"); + testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt"); + return 0; +} diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 6f0ed8bfd..1de15d79c 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library @@ -39,321 +41,298 @@ // Generated tables #include "tables_gen.hpp" -// Shared global buffers, we love you. These initial sizes are large -// enough to hold the largest books in Morrowind.esm, but we will -// resize automaticall if necessary. -static std::vector buf (50*1024); -static std::vector output (50*1024); -static int size; +using namespace ToUTF8; -// Make sure the given vector is large enough for 'size' bytes, -// including a terminating zero after it. -static void resize(std::vector &buf, size_t size) +Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): + mOutput(50*1024) { - if(buf.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - buf.resize(3*size); - - // And make sure the string is zero terminated - buf[size] = 0; + switch (sourceEncoding) + { + case ToUTF8::WINDOWS_1252: + { + translationArray = ToUTF8::windows_1252; + break; + } + case ToUTF8::WINDOWS_1250: + { + translationArray = ToUTF8::windows_1250; + break; + } + case ToUTF8::WINDOWS_1251: + { + translationArray = ToUTF8::windows_1251; + break; + } + default: + { + assert(0); + } + } } -// This is just used to spew out a reusable input buffer for the -// conversion process. -char *ToUTF8::getBuffer(int s) +std::string Utf8Encoder::getUtf8(const char* input, size_t size) { - // Remember the requested size - size = s; - resize(buf, size); - return &buf[0]; + // Double check that the input string stops at some point (it might + // contain zero terminators before this, inside its own data, which + // is also ok.) + assert(input[size] == 0); + + // TODO: The rest of this function is designed for single-character + // input encodings only. It also assumes that the input the input + // encoding shares its first 128 values (0-127) with ASCII. These + // conditions must be checked again if you add more input encodings + // later. + + // Compute output length, and check for pure ascii input at the same + // time. + bool ascii; + size_t outlen = getLength(input, ascii); + + // If we're pure ascii, then don't bother converting anything. + if(ascii) + return std::string(input, outlen); + + // Make sure the output is large enough + resize(outlen); + char *out = &mOutput[0]; + + // Translate + while (*input) + copyFromArray(*(input++), out); + + // Make sure that we wrote the correct number of bytes + assert((out-&mOutput[0]) == (int)outlen); + + // And make extra sure the output is null terminated + assert(mOutput.size() > outlen); + assert(mOutput[outlen] == 0); + + // Return a string + return std::string(&mOutput[0], outlen); +} + +std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) +{ + // Double check that the input string stops at some point (it might + // contain zero terminators before this, inside its own data, which + // is also ok.) + assert(input[size] == 0); + + // TODO: The rest of this function is designed for single-character + // input encodings only. It also assumes that the input the input + // encoding shares its first 128 values (0-127) with ASCII. These + // conditions must be checked again if you add more input encodings + // later. + + // Compute output length, and check for pure ascii input at the same + // time. + bool ascii; + size_t outlen = getLength2(input, ascii); + + // If we're pure ascii, then don't bother converting anything. + if(ascii) + return std::string(input, outlen); + + // Make sure the output is large enough + resize(outlen); + char *out = &mOutput[0]; + + // Translate + while(*input) + copyFromArray2(input, out); + + // Make sure that we wrote the correct number of bytes + assert((out-&mOutput[0]) == (int)outlen); + + // And make extra sure the output is null terminated + assert(mOutput.size() > outlen); + assert(mOutput[outlen] == 0); + + // Return a string + return std::string(&mOutput[0], outlen); +} + +// Make sure the output vector is large enough for 'size' bytes, +// including a terminating zero after it. +void Utf8Encoder::resize(size_t size) +{ + if (mOutput.size() <= size) + // Add some extra padding to reduce the chance of having to resize + // again later. + mOutput.resize(3*size); + + // And make sure the string is zero terminated + mOutput[size] = 0; } /** Get the total length length needed to decode the given string with - the given translation array. The arrays are encoded with 6 bytes - per character, with the first giving the length and the next 5 the - actual data. + the given translation array. The arrays are encoded with 6 bytes + per character, with the first giving the length and the next 5 the + actual data. - The function serves a dual purpose for optimization reasons: it - checks if the input is pure ascii (all values are <= 127). If this - is the case, then the ascii parameter is set to true, and the - caller can optimize for this case. + The function serves a dual purpose for optimization reasons: it + checks if the input is pure ascii (all values are <= 127). If this + is the case, then the ascii parameter is set to true, and the + caller can optimize for this case. */ -static size_t getLength(const char *arr, const char* input, bool &ascii) +size_t Utf8Encoder::getLength(const char* input, bool &ascii) { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; + ascii = true; + size_t len = 0; + const char* ptr = input; + unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost - // always the entire string.) - while(inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + // Do away with the ascii part of the string first (this is almost + // always the entire string.) + while (inp && inp < 128) + inp = *(++ptr); + len += (ptr-input); - // If we're not at the null terminator at this point, then there - // were some non-ascii characters to deal with. Go to slow-mode for - // the rest of the string. - if(inp) + // If we're not at the null terminator at this point, then there + // were some non-ascii characters to deal with. Go to slow-mode for + // the rest of the string. + if (inp) { - ascii = false; - while(inp) + ascii = false; + while (inp) { - // Find the translated length of this character in the - // lookup table. - len += arr[inp*6]; - inp = *(++ptr); + // Find the translated length of this character in the + // lookup table. + len += translationArray[inp*6]; + inp = *(++ptr); } } - return len; + return len; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -static void copyFromArray(const char *arr, unsigned char ch, char* &out) +void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) { - // Optimize for ASCII values - if(ch < 128) + // Optimize for ASCII values + if (ch < 128) { - *(out++) = ch; - return; + *(out++) = ch; + return; } - const char *in = arr + ch*6; - int len = *(in++); - for(int i=0; i outlen); - assert(output[outlen] == 0); - - // Return a string - return std::string(&output[0], outlen); -} - -static size_t getLength2(const char *arr, const char* input, bool &ascii) -{ - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - - // Do away with the ascii part of the string first (this is almost - // always the entire string.) - while(inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); - - // If we're not at the null terminator at this point, then there - // were some non-ascii characters to deal with. Go to slow-mode for - // the rest of the string. - if(inp) - { - ascii = false; - while(inp) + ascii = false; + while(inp) { len += 1; - // Find the translated length of this character in the - // lookup table. + // Find the translated length of this character in the + // lookup table. switch(inp) { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; + case 0xe2: len -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: len -= 1; break; } - inp = *(++ptr); + inp = *(++ptr); } } - return len; + return len; } -#include -#include - -static void copyFromArray2(const char *arr, char*& chp, char* &out) +void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) { unsigned char ch = *(chp++); - // Optimize for ASCII values - if(ch < 128) + // Optimize for ASCII values + if (ch < 128) { - *(out++) = ch; - return; + *(out++) = ch; + return; } - int len = 1; - switch (ch) - { - case 0xe2: len = 3; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len = 2; break; - } + int len = 1; + switch (ch) + { + case 0xe2: len = 3; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: len = 2; break; + } - if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space) - { - *(out++) = ch; - return; - } + if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space) + { + *(out++) = ch; + return; + } - unsigned char ch2 = *(chp++); - unsigned char ch3 = '\0'; - if (len == 3) - ch3 = *(chp++); + unsigned char ch2 = *(chp++); + unsigned char ch3 = '\0'; + if (len == 3) + ch3 = *(chp++); - for (int i = 128; i < 256; i++) - { - unsigned char b1 = arr[i*6 + 1], b2 = arr[i*6 + 2], b3 = arr[i*6 + 3]; - if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) - { - *(out++) = (char)i; - return; - } - } + for (int i = 128; i < 256; i++) + { + unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) + { + *(out++) = (char)i; + return; + } + } - std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; + std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; - *(out++) = ch; // Could not find glyph, just put whatever + *(out++) = ch; // Could not find glyph, just put whatever } -std::string ToUTF8::getLegacyEnc(ToUTF8::FromType to) +ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { - // Pick translation array - const char *arr; - switch (to) - { - case ToUTF8::WINDOWS_1252: - { - arr = ToUTF8::windows_1252; - break; - } - case ToUTF8::WINDOWS_1250: - { - arr = ToUTF8::windows_1250; - break; - } - case ToUTF8::WINDOWS_1251: - { - arr = ToUTF8::windows_1251; - break; - } - default: - { - assert(0); - } - } - - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - char* input = &buf[0]; - assert(input[size] == 0); - - // TODO: The rest of this function is designed for single-character - // input encodings only. It also assumes that the input the input - // encoding shares its first 128 values (0-127) with ASCII. These - // conditions must be checked again if you add more input encodings - // later. - - // Compute output length, and check for pure ascii input at the same - // time. - bool ascii; - size_t outlen = getLength2(arr, input, ascii); - - // If we're pure ascii, then don't bother converting anything. - if(ascii) - return std::string(input, outlen); - - // Make sure the output is large enough - resize(output, outlen); - char *out = &output[0]; - - // Translate - while(*input) - copyFromArray2(arr, input, out); - - // Make sure that we wrote the correct number of bytes - assert((out-&output[0]) == (int)outlen); - - // And make extra sure the output is null terminated - assert(output.size() > outlen); - assert(output[outlen] == 0); - - // Return a string - return std::string(&output[0], outlen); + if (encodingName == "win1250") + return ToUTF8::WINDOWS_1250; + else if (encodingName == "win1251") + return ToUTF8::WINDOWS_1251; + else + return ToUTF8::WINDOWS_1252; +} + +std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) +{ + if (encodingName == "win1250") + return "Using Central and Eastern European font encoding."; + else if (encodingName == "win1251") + return "Using Cyrillic font encoding."; + else + return "Using default (English) font encoding."; } diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 69e9fc92c..839aa36aa 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -2,26 +2,53 @@ #define COMPONENTS_TOUTF8_H #include +#include +#include namespace ToUTF8 { - // These are all the currently supported code pages - enum FromType + // These are all the currently supported code pages + enum FromType { - WINDOWS_1250, // Central ane Eastern European languages - WINDOWS_1251, // Cyrillic languages - WINDOWS_1252 // Used by English version of Morrowind (and - // probably others) + WINDOWS_1250, // Central ane Eastern European languages + WINDOWS_1251, // Cyrillic languages + WINDOWS_1252 // Used by English version of Morrowind (and + // probably others) }; - // Return a writable buffer of at least 'size' bytes. The buffer - // does not have to be freed. - char* getBuffer(int size); + FromType calculateEncoding(const std::string& encodingName); + std::string encodingUsingMessage(const std::string& encodingName); - // Convert the previously written buffer to UTF8 from the given code - // page. - std::string getUtf8(FromType from); - std::string getLegacyEnc(FromType to); + // class + + class Utf8Encoder + { + public: + Utf8Encoder(FromType sourceEncoding); + + // Convert to UTF8 from the previously given code page. + std::string getUtf8(const char *input, size_t size); + inline std::string getUtf8(const std::string &str) + { + return getUtf8(str.c_str(), str.size()); + } + + std::string getLegacyEnc(const char *input, size_t size); + inline std::string getLegacyEnc(const std::string &str) + { + return getLegacyEnc(str.c_str(), str.size()); + } + + private: + void resize(size_t size); + size_t getLength(const char* input, bool &ascii); + void copyFromArray(unsigned char chp, char* &out); + size_t getLength2(const char* input, bool &ascii); + void copyFromArray2(const char*& chp, char* &out); + + std::vector mOutput; + signed char* translationArray; + }; } #endif diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp new file mode 100644 index 000000000..d0ea4b7fb --- /dev/null +++ b/components/translation/translation.cpp @@ -0,0 +1,115 @@ +#include "translation.hpp" +#include + +#include + +namespace Translation +{ + void Storage::loadTranslationData(const Files::Collections& dataFileCollections, + const std::string& esmFileName) + { + std::string esmNameNoExtension(Misc::StringUtils::lowerCase(esmFileName)); + //changing the extension + size_t dotPos = esmNameNoExtension.rfind('.'); + if (dotPos != std::string::npos) + esmNameNoExtension.resize(dotPos); + + loadData(mCellNamesTranslations, esmNameNoExtension, ".cel", dataFileCollections); + loadData(mPhraseForms, esmNameNoExtension, ".top", dataFileCollections); + loadData(mTopicIDs, esmNameNoExtension, ".mrk", dataFileCollections); + } + + void Storage::loadData(ContainerType& container, + const std::string& fileNameNoExtension, + const std::string& extension, + const Files::Collections& dataFileCollections) + { + std::string fileName = fileNameNoExtension + extension; + + if (dataFileCollections.getCollection (extension).doesExist (fileName)) + { + std::string path = dataFileCollections.getCollection (extension).getPath (fileName).string(); + + std::ifstream stream (path.c_str()); + + if (!stream.is_open()) + throw std::runtime_error ("failed to open translation file: " + fileName); + + loadDataFromStream(container, stream); + } + } + + void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) + { + std::string line; + while (!stream.eof()) + { + std::getline( stream, line ); + if (!line.empty() && *line.rbegin() == '\r') + line.resize(line.size() - 1); + + if (!line.empty()) + { + line = mEncoder->getUtf8(line); + + size_t tab_pos = line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + { + std::string key = line.substr(0, tab_pos); + std::string value = line.substr(tab_pos + 1); + + if (!key.empty() && !value.empty()) + container.insert(std::make_pair(key, value)); + } + } + } + } + + std::string Storage::translateCellName(const std::string& cellName) const + { + std::map::const_iterator entry = + mCellNamesTranslations.find(cellName); + + if (entry == mCellNamesTranslations.end()) + return cellName; + + return entry->second; + } + + std::string Storage::topicID(const std::string& phrase) const + { + std::string result = topicStandardForm(phrase); + + //seeking for the topic ID + std::map::const_iterator topicIDIterator = + mTopicIDs.find(result); + + if (topicIDIterator != mTopicIDs.end()) + result = topicIDIterator->second; + + return result; + } + + std::string Storage::topicStandardForm(const std::string& phrase) const + { + std::map::const_iterator phraseFormsIterator = + mPhraseForms.find(phrase); + + if (phraseFormsIterator != mPhraseForms.end()) + return phraseFormsIterator->second; + else + return phrase; + } + + void Storage::setEncoder(ToUTF8::Utf8Encoder* encoder) + { + mEncoder = encoder; + } + + bool Storage::hasTranslation() const + { + return !mCellNamesTranslations.empty() || + !mTopicIDs.empty() || + !mPhraseForms.empty(); + } +} diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp new file mode 100644 index 000000000..bca9ea255 --- /dev/null +++ b/components/translation/translation.hpp @@ -0,0 +1,42 @@ +#ifndef COMPONENTS_TRANSLATION_DATA_H +#define COMPONENTS_TRANSLATION_DATA_H + +#include +#include + +namespace Translation +{ + class Storage + { + public: + + void loadTranslationData(const Files::Collections& dataFileCollections, + const std::string& esmFileName); + + std::string translateCellName(const std::string& cellName) const; + std::string topicID(const std::string& phrase) const; + + // Standard form usually means nominative case + std::string topicStandardForm(const std::string& phrase) const; + + void setEncoder(ToUTF8::Utf8Encoder* encoder); + + bool hasTranslation() const; + + private: + typedef std::map ContainerType; + + void loadData(ContainerType& container, + const std::string& fileNameNoExtension, + const std::string& extension, + const Files::Collections& dataFileCollections); + + void loadDataFromStream(ContainerType& container, std::istream& stream); + + + ToUTF8::Utf8Encoder* mEncoder; + ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms; + }; +} + +#endif diff --git a/credits.txt b/credits.txt index 063e9940a..d1e85c690 100644 --- a/credits.txt +++ b/credits.txt @@ -12,58 +12,75 @@ Marc Zinnschlag (Zini) - Lead Programmer/Project Manager Adam Hogan (aurix) Aleksandar Jovanov +Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) athile BrotherBrick +Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) Douglas Diniz (Dgdiniz) +Douglas Mencken (dougmencken) +Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 -gugus / gus +Emanuel Guével (potatoesmaster) +gugus/gus Jacob Essex (Yacoby) Jannik Heller (scrawl) Jason Hooks (jhooks) +Joel Graff (graffy) +Jordan Milne +Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) +Lars Söderberg (Lazaroth) +lazydev Leon Saunders (emoose) Lukasz Gromanowski (lgro) Marcin Hulist (Gohan) +Mark Siewert (mark76) +Manuel Edelmann (vorenon) Michael Mc Donnell Michael Papageorgiou (werdanith) +Nathan Jeffords (blunted2night) Nikolay Kasyanov (corristo) +Nolan Poe (nopoe) +Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) +Radu-Marius Popovici (rpopovici) Roman Melnik (Kromgart) +Sandy Carter (bwrsandman) Sebastian Wick (swick) -Sylvain T. (Garvek) +Sergey Shambir +Sylvain Thesnieres (Garvek) +Tom Mason (wheybags) Packagers: Alexander Olofsson (Ace) - Windows BrotherBrick - Ubuntu Linux -Edmondo Tommasina - Gentoo Linux +Edmondo Tommasina (edmondo) - Gentoo Linux +Julian Ospald (hasufell) - Gentoo Linux +Karl-Felix Glatzer (k1ll) - Linux Binaries Kenny Armstrong (artorius) - Fedora Linux Nikolay Kasyanov (corristo) - Mac OS X Sandy Carter (bwrsandman) - Arch Linux -Public Relations: -ElderTroll - Release Manager -sir_herrbatka - News Writer +Public Relations and Translations: +Artem Kotsynyak (greye) - Russian News Writer +Julien Voisin (jvoisin/ap0) - French News Writer +Mickey Lyle (raevol) - Release Manager +Pithorn - Chinese News Writer +sir_herrbatka - English/Polish News Writer WeirdSexy - Podcaster Website: -juanmnzsk8 - Spanish News Writer -Julien Voisin (jvoisin/ap0) - French News Writer -Kingpix - Italian News Writer Lukasz Gromanowski (lgro) - Website Administrator -Nikolay Kasyanov (corristo) - Russian News Writer -Okulo - Dutch News Writer -penguinroad - Indonesian News Writer Ryan Sardonic (Wry) - Wiki Editor -sir_herrbatka - Forum Admin/Polish News Writer -spyboot - German News Writer +sir_herrbatka - Forum Administrator Formula Research: @@ -79,20 +96,32 @@ Sadler Artwork: Necrod - OpenMW Logo -raevol - Wordpress Theme - +Mickey Lyle (raevol) - Wordpress Theme +Okulo - OpenMW Editor Icons Inactive Contributors: Ardekantur Armin Preiml +Carl Maxwell Diggory Hardy -Jan Borsodi +Dmitry Marakasov (AMDmi3) +ElderTroll +guidoj Jan-Peter Nilsson (peppe) +Jan Borsodi Josua Grawitter +juanmnzsk8 +Kingpix Lordrea +Michal Sciubidlo Nicolay Korslund +pchan3 +penguinroad +psi29a sergoz +spyboot Star-Demon +Thoronador Yuri Krupenin @@ -110,10 +139,10 @@ Thanks to Kevin Ryan, for creating the icon used for the Data Files tab of the OpenMW Launcher. Thanks to Georg Duffner, -for the open-source EB Garamond fontface. +for his EB Garamond fontface, see OFL.txt for his license terms. Thanks to Dongle, for his Daedric fontface, see Daedric Font License.txt for his license terms. -Thanks to Bitstream Inc. -for their Bitstream Vera fontface, see Bitstream Vera License.txt for their license terms. +Thanks to DejaVu team, +for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms. diff --git a/extern/shiny/CMakeLists.txt b/extern/shiny/CMakeLists.txt index 603336413..6eadcc167 100644 --- a/extern/shiny/CMakeLists.txt +++ b/extern/shiny/CMakeLists.txt @@ -24,13 +24,6 @@ set(SOURCE_FILES Main/ShaderSet.cpp ) -# In Debug mode, write the shader sources to the current directory -if (DEFINED CMAKE_BUILD_TYPE) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-DSHINY_WRITE_SHADER_DEBUG) - endif() -endif() - if (DEFINED SHINY_USE_WAVE_SYSTEM_INSTALL) # use system install else() diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index 678ee25c9..40c695fd4 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -15,6 +15,7 @@ namespace sh { Factory* Factory::sThis = 0; + const std::string Factory::mBinaryCacheName = "binaryCache"; Factory& Factory::getInstance() { @@ -50,7 +51,7 @@ namespace sh { assert(mCurrentLanguage != Language_None); - bool anyShaderDirty = false; + bool removeBinaryCache = false; if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) { @@ -182,24 +183,33 @@ namespace sh } } - std::string sourceFile = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); + std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); + std::string sourceRelative = it->second->findChild("source")->getValue(); ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile, - sourceFile, + sourceAbsolute, mPlatform->getBasePath(), it->first, &mGlobalSettings); - int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceFile)); - if (mShadersLastModified.find(sourceFile) != mShadersLastModified.end() - && mShadersLastModified[sourceFile] != lastModified) + int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); + mShadersLastModifiedNew[sourceRelative] = lastModified; + if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) { - newSet.markDirty (); - anyShaderDirty = true; + if (mShadersLastModified[sourceRelative] != lastModified) + { + // delete any outdated shaders based on this shader set + if (removeCache (it->first)) + removeBinaryCache = true; + } + } + else + { + // if we get here, this is either the first run or a new shader file was added + // in both cases we can safely delete + if (removeCache (it->first)) + removeBinaryCache = true; } - - mShadersLastModified[sourceFile] = lastModified; - mShaderSets.insert(std::make_pair(it->first, newSet)); } } @@ -224,7 +234,7 @@ namespace sh if (!mShadersEnabled) newInstance.setShadersEnabled (false); - newInstance.setSourceFile (it->second->m_fileName); + newInstance.setSourceFile (it->second->mFileName); std::vector props = it->second->getChildren(); for (std::vector::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt) @@ -293,9 +303,9 @@ namespace sh } } - if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !anyShaderDirty) + if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !removeBinaryCache) { - std::string file = mPlatform->getCacheFolder () + "/shShaderCache.txt"; + std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName; if (boost::filesystem::exists(file)) { mPlatform->deserializeShaders (file); @@ -307,17 +317,17 @@ namespace sh { if (mPlatform->supportsShaderSerialization () && mWriteMicrocodeCache) { - std::string file = mPlatform->getCacheFolder () + "/shShaderCache.txt"; + std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName; mPlatform->serializeShaders (file); } if (mReadSourceCache) { - // save the last modified time of shader sources + // save the last modified time of shader sources (as of when they were loaded) std::ofstream file; file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); - for (LastModifiedMap::const_iterator it = mShadersLastModified.begin(); it != mShadersLastModified.end(); ++it) + for (LastModifiedMap::const_iterator it = mShadersLastModifiedNew.begin(); it != mShadersLastModifiedNew.end(); ++it) { file << it->first << "\n" << it->second << std::endl; } @@ -580,4 +590,44 @@ namespace sh assert(m); m->createForConfiguration (configuration, 0); } + + bool Factory::removeCache(const std::string& pattern) + { + bool ret = false; + if ( boost::filesystem::exists(mPlatform->getCacheFolder()) + && boost::filesystem::is_directory(mPlatform->getCacheFolder())) + { + boost::filesystem::directory_iterator end_iter; + for( boost::filesystem::directory_iterator dir_iter(mPlatform->getCacheFolder()) ; dir_iter != end_iter ; ++dir_iter) + { + if (boost::filesystem::is_regular_file(dir_iter->status()) ) + { + boost::filesystem::path file = dir_iter->path(); + + std::string pathname = file.filename().string(); + + // get first part of filename, e.g. main_fragment_546457654 -> main_fragment + // there is probably a better method for this... + std::vector tokens; + boost::split(tokens, pathname, boost::is_any_of("_")); + tokens.erase(--tokens.end()); + std::string shaderName; + for (std::vector::const_iterator vector_iter = tokens.begin(); vector_iter != tokens.end();) + { + shaderName += *(vector_iter++); + if (vector_iter != tokens.end()) + shaderName += "_"; + } + + if (shaderName == pattern) + { + boost::filesystem::remove(file); + ret = true; + std::cout << "Removing outdated shader: " << file << std::endl; + } + } + } + } + return ret; + } } diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 799dd71eb..846860b89 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -185,6 +185,7 @@ namespace sh ConfigurationMap mConfigurations; LodConfigurationMap mLodConfigurations; LastModifiedMap mShadersLastModified; + LastModifiedMap mShadersLastModifiedNew; PropertySetGet mGlobalSettings; @@ -201,6 +202,11 @@ namespace sh MaterialInstance* findInstance (const std::string& name); MaterialInstance* searchInstance (const std::string& name); + + /// @return was anything removed? + bool removeCache (const std::string& pattern); + + static const std::string mBinaryCacheName; }; } diff --git a/extern/shiny/Main/ScriptLoader.cpp b/extern/shiny/Main/ScriptLoader.cpp index a8971dc87..93d728b02 100644 --- a/extern/shiny/Main/ScriptLoader.cpp +++ b/extern/shiny/Main/ScriptLoader.cpp @@ -14,9 +14,9 @@ namespace sh for ( boost::filesystem::recursive_directory_iterator end, dir(path); dir != end; ++dir ) { boost::filesystem::path p(*dir); - if(p.extension() == c->m_fileEnding) + if(p.extension() == c->mFileEnding) { - c->m_currentFileName = (*dir).path().string(); + c->mCurrentFileName = (*dir).path().string(); std::ifstream in((*dir).path().string().c_str(), std::ios::binary); c->parseScript(in); } @@ -25,7 +25,7 @@ namespace sh ScriptLoader::ScriptLoader(const std::string& fileEnding) { - m_fileEnding = fileEnding; + mFileEnding = fileEnding; } ScriptLoader::~ScriptLoader() @@ -70,7 +70,7 @@ namespace sh { //Get first token _nextToken(stream); - if (tok == TOKEN_EOF) + if (mToken == TOKEN_EOF) { stream.close(); return; @@ -87,7 +87,7 @@ namespace sh //EOF token if (!stream.good()) { - tok = TOKEN_EOF; + mToken = TOKEN_EOF; return; } @@ -101,7 +101,7 @@ namespace sh if (!stream.good()) { - tok = TOKEN_EOF; + mToken = TOKEN_EOF; return; } @@ -115,21 +115,21 @@ namespace sh stream.unget(); - tok = TOKEN_NewLine; + mToken = TOKEN_NewLine; return; } //Open brace token else if (ch == '{') { - tok = TOKEN_OpenBrace; + mToken = TOKEN_OpenBrace; return; } //Close brace token else if (ch == '}') { - tok = TOKEN_CloseBrace; + mToken = TOKEN_CloseBrace; return; } @@ -139,8 +139,8 @@ namespace sh throw std::runtime_error("Parse Error: Invalid character, ConfigLoader::load()"); } - tokVal = ""; - tok = TOKEN_Text; + mTokenValue = ""; + mToken = TOKEN_Text; do { //Skip comments @@ -157,13 +157,13 @@ namespace sh ch = stream.get(); } while (ch != '\r' && ch != '\n' && !stream.eof()); - tok = TOKEN_NewLine; + mToken = TOKEN_NewLine; return; } } //Add valid char to tokVal - tokVal += (char)ch; + mTokenValue += (char)ch; //Next char ch = stream.get(); @@ -177,7 +177,7 @@ namespace sh void ScriptLoader::_skipNewLines(std::ifstream &stream) { - while (tok == TOKEN_NewLine) + while (mToken == TOKEN_NewLine) { _nextToken(stream); } @@ -189,7 +189,7 @@ namespace sh while (true) { - switch (tok) + switch (mToken) { //Node case TOKEN_Text: @@ -198,23 +198,23 @@ namespace sh ScriptNode *newNode; if (parent) { - newNode = parent->addChild(tokVal); + newNode = parent->addChild(mTokenValue); } else { - newNode = new ScriptNode(0, tokVal); + newNode = new ScriptNode(0, mTokenValue); } //Get values _nextToken(stream); std::string valueStr; int i=0; - while (tok == TOKEN_Text) + while (mToken == TOKEN_Text) { if (i == 0) - valueStr += tokVal; + valueStr += mTokenValue; else - valueStr += " " + tokVal; + valueStr += " " + mTokenValue; _nextToken(stream); ++i; } @@ -235,13 +235,13 @@ namespace sh _skipNewLines(stream); //Add any sub-nodes - if (tok == TOKEN_OpenBrace) + if (mToken == TOKEN_OpenBrace) { //Parse nodes _nextToken(stream); _parseNodes(stream, newNode); //Check for matching closing brace - if (tok != TOKEN_CloseBrace) + if (mToken != TOKEN_CloseBrace) { throw std::runtime_error("Parse Error: Expecting closing brace"); } @@ -249,7 +249,7 @@ namespace sh _skipNewLines(stream); } - newNode->m_fileName = m_currentFileName; + newNode->mFileName = mCurrentFileName; break; } @@ -276,16 +276,16 @@ namespace sh ScriptNode::ScriptNode(ScriptNode *parent, const std::string &name) { - m_name = name; - m_parent = parent; - _removeSelf = true; //For proper destruction - m_lastChildFound = -1; + mName = name; + mParent = parent; + mRemoveSelf = true; //For proper destruction + mLastChildFound = -1; //Add self to parent's child list (unless this is the root node being created) if (parent != NULL) { - m_parent->m_children.push_back(this); - _iter = --(m_parent->m_children.end()); + mParent->mChildren.push_back(this); + mIter = --(mParent->mChildren.end()); } } @@ -293,18 +293,18 @@ namespace sh { //Delete all children std::vector::iterator i; - for (i = m_children.begin(); i != m_children.end(); i++) + for (i = mChildren.begin(); i != mChildren.end(); i++) { ScriptNode *node = *i; - node->_removeSelf = false; + node->mRemoveSelf = false; delete node; } - m_children.clear(); + mChildren.clear(); //Remove self from parent's child list - if (_removeSelf && m_parent != NULL) + if (mRemoveSelf && mParent != NULL) { - m_parent->m_children.erase(_iter); + mParent->mChildren.erase(mIter); } } @@ -324,20 +324,20 @@ namespace sh ScriptNode *ScriptNode::findChild(const std::string &name, bool recursive) { int indx, prevC, nextC; - int childCount = (int)m_children.size(); + int childCount = (int)mChildren.size(); - if (m_lastChildFound != -1) + if (mLastChildFound != -1) { //If possible, try checking the nodes neighboring the last successful search //(often nodes searched for in sequence, so this will boost search speeds). - prevC = m_lastChildFound-1; if (prevC < 0) prevC = 0; else if (prevC >= childCount) prevC = childCount-1; - nextC = m_lastChildFound+1; if (nextC < 0) nextC = 0; else if (nextC >= childCount) nextC = childCount-1; + prevC = mLastChildFound-1; if (prevC < 0) prevC = 0; else if (prevC >= childCount) prevC = childCount-1; + nextC = mLastChildFound+1; if (nextC < 0) nextC = 0; else if (nextC >= childCount) nextC = childCount-1; for (indx = prevC; indx <= nextC; ++indx) { - ScriptNode *node = m_children[indx]; - if (node->m_name == name) + ScriptNode *node = mChildren[indx]; + if (node->mName == name) { - m_lastChildFound = indx; + mLastChildFound = indx; return node; } } @@ -346,17 +346,17 @@ namespace sh //already searched area above. for (indx = nextC + 1; indx < childCount; ++indx) { - ScriptNode *node = m_children[indx]; - if (node->m_name == name) { - m_lastChildFound = indx; + ScriptNode *node = mChildren[indx]; + if (node->mName == name) { + mLastChildFound = indx; return node; } } for (indx = 0; indx < prevC; ++indx) { - ScriptNode *node = m_children[indx]; - if (node->m_name == name) { - m_lastChildFound = indx; + ScriptNode *node = mChildren[indx]; + if (node->mName == name) { + mLastChildFound = indx; return node; } } @@ -365,9 +365,9 @@ namespace sh { //Search for the node from start to finish for (indx = 0; indx < childCount; ++indx){ - ScriptNode *node = m_children[indx]; - if (node->m_name == name) { - m_lastChildFound = indx; + ScriptNode *node = mChildren[indx]; + if (node->mName == name) { + mLastChildFound = indx; return node; } } @@ -378,7 +378,7 @@ namespace sh { for (indx = 0; indx < childCount; ++indx) { - m_children[indx]->findChild(name, recursive); + mChildren[indx]->findChild(name, recursive); } } @@ -389,13 +389,13 @@ namespace sh void ScriptNode::setParent(ScriptNode *newParent) { //Remove self from current parent - m_parent->m_children.erase(_iter); + mParent->mChildren.erase(mIter); //Set new parent - m_parent = newParent; + mParent = newParent; //Add self to new parent - m_parent->m_children.push_back(this); - _iter = --(m_parent->m_children.end()); + mParent->mChildren.push_back(this); + mIter = --(mParent->mChildren.end()); } } diff --git a/extern/shiny/Main/ScriptLoader.hpp b/extern/shiny/Main/ScriptLoader.hpp index caf743bd2..89720fb5d 100644 --- a/extern/shiny/Main/ScriptLoader.hpp +++ b/extern/shiny/Main/ScriptLoader.hpp @@ -23,7 +23,7 @@ namespace sh ScriptLoader(const std::string& fileEnding); virtual ~ScriptLoader(); - std::string m_fileEnding; + std::string mFileEnding; // For a line like // entity animals/dog @@ -38,11 +38,11 @@ namespace sh void parseScript(std::ifstream &stream); - std::string m_currentFileName; + std::string mCurrentFileName; protected: - float m_LoadOrder; + float mLoadOrder; // like "*.object" std::map m_scriptList; @@ -56,8 +56,8 @@ namespace sh TOKEN_EOF }; - Token tok, lastTok; - std::string tokVal; + Token mToken, mLastToken; + std::string mTokenValue; void _parseNodes(std::ifstream &stream, ScriptNode *parent); void _nextToken(std::ifstream &stream); @@ -74,22 +74,22 @@ namespace sh inline void setName(const std::string &name) { - this->m_name = name; + this->mName = name; } inline std::string &getName() { - return m_name; + return mName; } inline void setValue(const std::string &value) { - m_value = value; + mValue = value; } inline std::string &getValue() { - return m_value; + return mValue; } ScriptNode *addChild(const std::string &name = "untitled", bool replaceExisting = false); @@ -97,36 +97,36 @@ namespace sh inline std::vector &getChildren() { - return m_children; + return mChildren; } inline ScriptNode *getChild(unsigned int index = 0) { - assert(index < m_children.size()); - return m_children[index]; + assert(index < mChildren.size()); + return mChildren[index]; } void setParent(ScriptNode *newParent); inline ScriptNode *getParent() { - return m_parent; + return mParent; } - std::string m_fileName; + std::string mFileName; private: - std::string m_name; - std::string m_value; - std::vector m_children; - ScriptNode *m_parent; + std::string mName; + std::string mValue; + std::vector mChildren; + ScriptNode *mParent; - int m_lastChildFound; //The last child node's index found with a call to findChild() + int mLastChildFound; //The last child node's index found with a call to findChild() - std::vector::iterator _iter; - bool _removeSelf; + std::vector::iterator mIter; + bool mRemoveSelf; }; } diff --git a/extern/shiny/Main/ShaderInstance.cpp b/extern/shiny/Main/ShaderInstance.cpp index 07ef8dfe2..1539128ab 100644 --- a/extern/shiny/Main/ShaderInstance.cpp +++ b/extern/shiny/Main/ShaderInstance.cpp @@ -337,8 +337,7 @@ namespace sh size_t pos; bool readCache = Factory::getInstance ().getReadSourceCache () && boost::filesystem::exists( - Factory::getInstance ().getCacheFolder () + "/" + mName) - && !mParent->isDirty (); + Factory::getInstance ().getCacheFolder () + "/" + mName); bool writeCache = Factory::getInstance ().getWriteSourceCache (); @@ -363,12 +362,6 @@ namespace sh if (Factory::getInstance ().getShaderDebugOutputEnabled ()) writeDebugFile(source, name + ".pre"); - else - { - #ifdef SHINY_WRITE_SHADER_DEBUG - writeDebugFile(source, name + ".pre"); - #endif - } // why do we need our own preprocessor? there are several custom commands available in the shader files // (for example for binding uniforms to properties or auto constants) - more below. it is important that these @@ -648,12 +641,6 @@ namespace sh if (Factory::getInstance ().getShaderDebugOutputEnabled ()) writeDebugFile(source, name); - else - { -#ifdef SHINY_WRITE_SHADER_DEBUG - writeDebugFile(source, name); -#endif - } if (!mProgram->getSupported()) { diff --git a/extern/shiny/Main/ShaderSet.cpp b/extern/shiny/Main/ShaderSet.cpp index 2702ece19..413d7d1a2 100644 --- a/extern/shiny/Main/ShaderSet.cpp +++ b/extern/shiny/Main/ShaderSet.cpp @@ -17,7 +17,6 @@ namespace sh , mName(name) , mCgProfile(cgProfile) , mHlslProfile(hlslProfile) - , mIsDirty(false) { if (type == "vertex") mType = GPT_Vertex; diff --git a/extern/shiny/Main/ShaderSet.hpp b/extern/shiny/Main/ShaderSet.hpp index 776750598..a423b6779 100644 --- a/extern/shiny/Main/ShaderSet.hpp +++ b/extern/shiny/Main/ShaderSet.hpp @@ -30,9 +30,6 @@ namespace sh /// so it does not matter if you pass any extra properties that the shader does not care about. ShaderInstance* getInstance (PropertySetGet* properties); - void markDirty() { mIsDirty = true; } - ///< Signals that the cache is out of date, and thus should not be used this time - private: PropertySetGet* getCurrentGlobalSettings() const; std::string getBasePath() const; @@ -41,12 +38,8 @@ namespace sh std::string getHlslProfile() const; int getType() const; - bool isDirty() { return mIsDirty; } - friend class ShaderInstance; - bool mIsDirty; - private: GpuProgramType mType; std::string mSource; diff --git a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp index 9f57c7b44..4ec43fcae 100644 --- a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp +++ b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp @@ -1,5 +1,7 @@ #include "OgreMaterialSerializer.hpp" +#include + namespace sh { void OgreMaterialSerializer::reset() @@ -19,6 +21,13 @@ namespace sh bool OgreMaterialSerializer::setPassProperty (const std::string& param, std::string value, Ogre::Pass* pass) { + // workaround https://ogre3d.atlassian.net/browse/OGRE-158 + if (param == "transparent_sorting" && value == "force") + { + pass->setTransparentSortingForced(true); + return true; + } + reset(); mScriptContext.section = Ogre::MSS_PASS; diff --git a/extern/shiny/Platforms/Ogre/OgrePass.cpp b/extern/shiny/Platforms/Ogre/OgrePass.cpp index 8cfaae078..3ed48b96f 100644 --- a/extern/shiny/Platforms/Ogre/OgrePass.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePass.cpp @@ -50,13 +50,6 @@ namespace sh return true; // handled already else if (name == "fragment_program") return true; // handled already - else if (name == "ffp_vertex_colour_ambient") - { - bool enabled = retrieveValue(value, context).get(); - // fixed-function vertex colour tracking - mPass->setVertexColourTracking(enabled ? Ogre::TVC_AMBIENT : Ogre::TVC_NONE); - return true; - } else { OgreMaterialSerializer& s = OgrePlatform::getSerializer(); diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index e8426afb7..9e65b516b 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,12 +1,8 @@ project(resources) set(WATER_FILES - underwater_dome.mesh water_nm.png -) - -set(GBUFFER_FILES - gbuffer.compositor + circle.png ) set(MATERIAL_FILES @@ -21,7 +17,6 @@ set(MATERIAL_FILES objects.shader objects.shaderset openmw.configuration - quad2.shader quad.mat quad.shader quad.shaderset @@ -43,10 +38,14 @@ set(MATERIAL_FILES selection.mat selection.shader selection.shaderset + watersim_heightmap.shader + watersim_addimpulse.shader + watersim_heighttonormal.shader + watersim_common.h + watersim.mat + watersim.shaderset ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water "${OpenMW_BINARY_DIR}/resources/water/" "${WATER_FILES}") -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer "${OpenMW_BINARY_DIR}/resources/gbuffer/" "${GBUFFER_FILES}") - copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials "${OpenMW_BINARY_DIR}/resources/materials/" "${MATERIAL_FILES}") diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor deleted file mode 100644 index 04600ce9b..000000000 --- a/files/gbuffer/gbuffer.compositor +++ /dev/null @@ -1,91 +0,0 @@ -// Compositor that just controls output to the MRT textures -compositor gbuffer -{ - technique - { - // MRT output. Currently this is a color texture plus a depth texture - texture mrt_output target_width target_height PF_FLOAT16_RGBA PF_FLOAT16_RGBA chain_scope depth_pool 2 - - target mrt_output - { - input none - - pass clear - { - colour_value 0 0 0 1 - } - pass render_quad - { - // this makes sure the depth for background is set to 1 - material openmw_viewport_init - } - pass render_scene - { - // Renders everything except water - first_render_queue 0 - last_render_queue 50 - } - - } - - target_output - { - input none - - pass render_quad - { - material quad - input 0 mrt_output 0 - } - } - } -} - -// Finalizer compositor to render objects that we don't want in the MRT textures (ex. water) -// NB the water has to be rendered in a seperate compositor anyway, because it -// accesses the MRT textures which can't be done while they are still being rendered to. -compositor gbufferFinalizer -{ - technique - { - texture no_mrt_output target_width target_height PF_R8G8B8A8 depth_pool 2 no_fsaa - texture previousscene target_width target_height PF_R8G8B8A8 - - target previousscene - { - input previous - } - target no_mrt_output - { - input none - shadows off - pass clear - { - buffers colour - colour_value 0 0 0 0 - } - pass render_quad - { - material quad_noDepthWrite - input 0 previousscene - } - pass render_scene - { - first_render_queue 51 - last_render_queue 100 - } - } - target_output - { - input none - pass clear - { - } - pass render_quad - { - material quad_noDepthWrite - input 0 no_mrt_output - } - } - } -} diff --git a/files/launcher.qss b/files/launcher.qss deleted file mode 100644 index 1eb056d4d..000000000 --- a/files/launcher.qss +++ /dev/null @@ -1,123 +0,0 @@ -#PlayGroup { - background-image: url(":/images/playpage-background.png"); - background-repeat: no-repeat; - background-position: top; - padding-left: 30px; - padding-right: 30px; -} - -#MastersWidget { - selection-background-color: palette(highlight); -} - -#PlayButton { - height: 50px; - margin-bottom: 30px; - - background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(255, 255, 255, 200), - stop:0.1 rgba(255, 255, 255, 15), - stop:0.49 rgba(255, 255, 255, 75), - stop:0.5 rgba(0, 0, 0, 0), - stop:0.9 rgba(0, 0, 0, 55), - stop:1 rgba(0, 0, 0, 100)); - - font-size: 26pt; - font-family: "EB Garamond", "EB Garamond 08"; - color: black; - - border-right: 1px solid rgba(0, 0, 0, 155); - border-left: 1px solid rgba(0, 0, 0, 55); - border-top: 1px solid rgba(0, 0, 0, 55); - border-bottom: 1px solid rgba(0, 0, 0, 155); - - border-radius: 5px; -} - -#PlayButton:hover { - border-bottom: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); - border-top: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); - border-right: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); - border-left: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); - border-width: 2px; - border-style: solid; -} - -#PlayButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(0, 0, 0, 75), - stop:0.1 rgba(0, 0, 0, 15), - stop:0.2 rgba(255, 255, 255, 55) - stop:0.95 rgba(255, 255, 255, 55), - stop:1 rgba(255, 255, 255, 155)); - - border: 1px solid rgba(0, 0, 0, 55); -} - -#ProfileLabel { - font-size: 18pt; - font-family: "EB Garamond", "EB Garamond 08"; -} - -#ProfilesComboBox { - padding: 1px 18px 1px 3px; - - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, stop:0.2 rgba(0, 0, 0, 25), stop:1 white); - border-width: 1px; - border-color: rgba(0, 0, 0, 125); - border-style: solid; - border-radius: 2px; -} - -/*QComboBox gets the "on" state when the popup is open */ -#ProfilesComboBox:!editable:on, #ProfilesComboBox::drop-down:editable:on { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(0, 0, 0, 75), - stop:0.1 rgba(0, 0, 0, 15), - stop:0.2 rgba(255, 255, 255, 55)); - - border: 1px solid rgba(0, 0, 0, 55); -} - - -#ProfilesComboBox { /* shift the text when the popup opens */ - padding-top: 3px; - padding-left: 4px; - - font-size: 12pt; - font-family: "EB Garamond", "EB Garamond 08"; -} - -#ProfilesComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: top right; - - border-width: 1px; - border-left-width: 1px; - border-left-color: darkgray; - border-left-style: solid; /* just a single line */ - border-top-right-radius: 3px; /* same radius as the QComboBox */ - border-bottom-right-radius: 3px; -} - -#ProfilesComboBox::down-arrow { - image: url(":/images/down.png"); -} - -#ProfilesComboBox::down-arrow:on { /* shift the arrow when popup is open */ - top: 1px; - left: 1px; -} - -#ProfilesComboBox QAbstractItemView { - border: 2px solid lightgray; - border-radius: 5px; -} - -#IconWidget { - background-image: url(":/images/openmw-header.png"); - background-color: white; - background-repeat: no-repeat; - background-attachment: scroll; - background-position: right; -} diff --git a/apps/launcher/resources/icons/tango/document-new.png b/files/launcher/icons/tango/document-new.png similarity index 100% rename from apps/launcher/resources/icons/tango/document-new.png rename to files/launcher/icons/tango/document-new.png diff --git a/apps/launcher/resources/icons/tango/edit-copy.png b/files/launcher/icons/tango/edit-copy.png similarity index 100% rename from apps/launcher/resources/icons/tango/edit-copy.png rename to files/launcher/icons/tango/edit-copy.png diff --git a/apps/launcher/resources/icons/tango/edit-delete.png b/files/launcher/icons/tango/edit-delete.png similarity index 100% rename from apps/launcher/resources/icons/tango/edit-delete.png rename to files/launcher/icons/tango/edit-delete.png diff --git a/apps/launcher/resources/icons/tango/go-bottom.png b/files/launcher/icons/tango/go-bottom.png similarity index 100% rename from apps/launcher/resources/icons/tango/go-bottom.png rename to files/launcher/icons/tango/go-bottom.png diff --git a/apps/launcher/resources/icons/tango/go-down.png b/files/launcher/icons/tango/go-down.png similarity index 100% rename from apps/launcher/resources/icons/tango/go-down.png rename to files/launcher/icons/tango/go-down.png diff --git a/apps/launcher/resources/icons/tango/go-top.png b/files/launcher/icons/tango/go-top.png similarity index 100% rename from apps/launcher/resources/icons/tango/go-top.png rename to files/launcher/icons/tango/go-top.png diff --git a/apps/launcher/resources/icons/tango/go-up.png b/files/launcher/icons/tango/go-up.png similarity index 100% rename from apps/launcher/resources/icons/tango/go-up.png rename to files/launcher/icons/tango/go-up.png diff --git a/apps/launcher/resources/icons/tango/index.theme b/files/launcher/icons/tango/index.theme similarity index 100% rename from apps/launcher/resources/icons/tango/index.theme rename to files/launcher/icons/tango/index.theme diff --git a/apps/launcher/resources/icons/tango/video-display.png b/files/launcher/icons/tango/video-display.png similarity index 100% rename from apps/launcher/resources/icons/tango/video-display.png rename to files/launcher/icons/tango/video-display.png diff --git a/apps/launcher/resources/images/clear.png b/files/launcher/images/clear.png similarity index 100% rename from apps/launcher/resources/images/clear.png rename to files/launcher/images/clear.png diff --git a/apps/launcher/resources/images/down.png b/files/launcher/images/down.png similarity index 100% rename from apps/launcher/resources/images/down.png rename to files/launcher/images/down.png diff --git a/apps/launcher/resources/images/openmw-header.png b/files/launcher/images/openmw-header.png similarity index 100% rename from apps/launcher/resources/images/openmw-header.png rename to files/launcher/images/openmw-header.png diff --git a/apps/launcher/resources/images/openmw-plugin.png b/files/launcher/images/openmw-plugin.png similarity index 100% rename from apps/launcher/resources/images/openmw-plugin.png rename to files/launcher/images/openmw-plugin.png diff --git a/apps/launcher/resources/images/openmw.ico b/files/launcher/images/openmw.ico similarity index 100% rename from apps/launcher/resources/images/openmw.ico rename to files/launcher/images/openmw.ico diff --git a/apps/launcher/resources/images/openmw.png b/files/launcher/images/openmw.png similarity index 100% rename from apps/launcher/resources/images/openmw.png rename to files/launcher/images/openmw.png diff --git a/apps/launcher/resources/images/playpage-background.png b/files/launcher/images/playpage-background.png similarity index 100% rename from apps/launcher/resources/images/playpage-background.png rename to files/launcher/images/playpage-background.png diff --git a/files/launcher/launcher.qrc b/files/launcher/launcher.qrc new file mode 100644 index 000000000..19b1c5a6f --- /dev/null +++ b/files/launcher/launcher.qrc @@ -0,0 +1,21 @@ + + + images/clear.png + images/down.png + images/openmw.png + images/openmw-plugin.png + images/openmw-header.png + images/playpage-background.png + + + icons/tango/index.theme + icons/tango/video-display.png + icons/tango/document-new.png + icons/tango/edit-copy.png + icons/tango/edit-delete.png + icons/tango/go-bottom.png + icons/tango/go-down.png + icons/tango/go-top.png + icons/tango/go-up.png + + diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index 295fa9376..eb05c3e18 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -1,37 +1,28 @@ #include "core.h" -#define MRT @shGlobalSettingBool(mrt_output) - #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shColourInput(float4) - shOutput(float4, colourPassthrough) + shOutput(float, alphaFade) SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); - colourPassthrough = colour; + alphaFade = shInputPosition.z < 150.0 ? 0.0 : 1.0; } #else SH_BEGIN_PROGRAM - shInput(float4, colourPassthrough) -#if MRT - shDeclareMrtOutput(1) -#endif + shInput(float, alphaFade) shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour) + shUniform(float4, horizonColour) @shSharedParameter(horizonColour, horizonColour) SH_START_PROGRAM { - shOutputColour(0) = colourPassthrough * atmosphereColour; - -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif + shOutputColour(0) = alphaFade * atmosphereColour + (1.f - alphaFade) * horizonColour; } #endif diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index f4258bf5d..e60026d3b 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -1,31 +1,25 @@ #include "core.h" -#define MRT @shGlobalSettingBool(mrt_output) - #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) - shColourInput(float4) - shOutput(float4, colourPassthrough) + shOutput(float, alphaFade) SH_START_PROGRAM { - colourPassthrough = colour; shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; + alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0; } #else SH_BEGIN_PROGRAM shInput(float2, UV) - shInput(float4, colourPassthrough) -#if MRT - shDeclareMrtOutput(1) -#endif + shInput(float, alphaFade) shSampler2D(diffuseMap1) shSampler2D(diffuseMap2) @@ -42,11 +36,7 @@ float4 albedo = shSample(diffuseMap1, scrolledUV) * (1-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor; - shOutputColour(0) = colourPassthrough * float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity); - -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif + shOutputColour(0) = float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity * alphaFade); } #endif diff --git a/files/materials/core.h b/files/materials/core.h index 1c9ea1d1d..3385e5fac 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -1,8 +1,3 @@ -#define gammaCorrectRead(v) pow(max(v, 0.00001f), float3(gammaCorrection,gammaCorrection,gammaCorrection)) -#define gammaCorrectOutput(v) pow(max(v, 0.00001f), float3(1.f/gammaCorrection,1.f/gammaCorrection,1.f/gammaCorrection)) - - - #if SH_HLSL == 1 || SH_CG == 1 #define shTexture2D sampler2D @@ -27,6 +22,8 @@ #define shNormalInput(type) , in type normal : NORMAL #define shColourInput(type) , in type colour : COLOR + + #define shFract(val) frac(val) #ifdef SH_VERTEX_SHADER @@ -63,6 +60,8 @@ #if SH_GLSL == 1 + #define shFract(val) fract(val) + @version 120 #define float2 vec2 diff --git a/files/materials/moon.shader b/files/materials/moon.shader index 02f3d8001..231f60ba0 100644 --- a/files/materials/moon.shader +++ b/files/materials/moon.shader @@ -1,8 +1,5 @@ #include "core.h" -#define MRT @shGlobalSettingBool(mrt_output) - - #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM @@ -22,9 +19,6 @@ shSampler2D(diffuseMap) shSampler2D(alphaMap) shInput(float2, UV) -#if MRT - shDeclareMrtOutput(1) -#endif shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) @@ -42,10 +36,6 @@ shOutputColour(0).rgb += (1-tex.a) * shOutputColour(0).a * atmosphereColour.rgb; //fill dark side of moon with atmosphereColour shOutputColour(0).rgb += (1-materialDiffuse.a) * atmosphereColour.rgb; //fade bump -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif - } #endif diff --git a/files/materials/objects.mat b/files/materials/objects.mat index f1198b4a2..5e18a666a 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -1,16 +1,17 @@ material openmw_objects_base { diffuse 1.0 1.0 1.0 1.0 - specular 0.4 0.4 0.4 32 + specular 0 0 0 0 ambient 1.0 1.0 1.0 emissive 0.0 0.0 0.0 - has_vertex_colour false + vertmode 0 diffuseMap black.png is_transparent false // real transparency, alpha rejection doesn't count here scene_blend default depth_write default alpha_rejection default + transparent_sorting default pass { @@ -19,7 +20,7 @@ material openmw_objects_base shader_properties { - has_vertex_colour $has_vertex_colour + vertexcolor_mode $vertmode is_transparent $is_transparent } @@ -30,9 +31,7 @@ material openmw_objects_base scene_blend $scene_blend alpha_rejection $alpha_rejection depth_write $depth_write - - ffp_vertex_colour_ambient $has_vertex_colour - + transparent_sorting $transparent_sorting texture_unit diffuseMap { @@ -58,10 +57,5 @@ material openmw_objects_base tex_address_mode clamp filtering none } - - texture_unit causticMap - { - direct_texture water_nm.png - } } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 5ea076342..46e3abeb3 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -2,25 +2,26 @@ #define FOG @shGlobalSettingBool(fog) -#define MRT @shPropertyNotBool(is_transparent) && @shGlobalSettingBool(mrt_output) -#define LIGHTING @shGlobalSettingBool(lighting) -#define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) -#define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) +#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) +#define SHADOWS @shGlobalSettingBool(shadows) #if SHADOWS || SHADOWS_PSSM #include "shadows.h" #endif -#if FOG || MRT || SHADOWS_PSSM +#if FOG || SHADOWS_PSSM #define NEED_DEPTH #endif -#define UNDERWATER @shGlobalSettingBool(underwater_effects) && LIGHTING +#define UNDERWATER @shGlobalSettingBool(render_refraction) +#define VERTEXCOLOR_MODE @shPropertyString(vertexcolor_mode) -#define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) +#define VERTEX_LIGHTING 1 + +#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) #ifdef SH_VERTEX_SHADER @@ -28,6 +29,16 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + +#if (VIEWPROJ_FIX) || (SHADOWS) + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) +#endif + +#if VIEWPROJ_FIX + shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) + shUniform(float4x4, vpMatrix) @shAutoConstant(vpMatrix, viewproj_matrix) +#endif + shVertexInput(float2, uv0) shOutput(float2, UV) shNormalInput(float4) @@ -35,20 +46,33 @@ shOutput(float, depthPassthrough) #endif -#if LIGHTING - shOutput(float3, normalPassthrough) shOutput(float3, objSpacePositionPassthrough) + +#if VERTEXCOLOR_MODE != 0 + shColourInput(float4) +#endif + +#if VERTEX_LIGHTING + shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) + shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) +#if VERTEXCOLOR_MODE != 2 + shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) +#endif +#if VERTEXCOLOR_MODE != 2 + shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) +#endif +#if VERTEXCOLOR_MODE != 1 + shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) #endif -#if HAS_VERTEXCOLOR - shColourInput(float4) - shOutput(float4, colourPassthrough) #endif #if SHADOWS shOutput(float4, lightSpacePos0) shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) #endif #if SHADOWS_PSSM @@ -56,27 +80,44 @@ shOutput(float4, lightSpacePos@shIterator) shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) @shEndForeach - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) +#if !VIEWPROJ_FIX + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) +#endif +#endif + +#if VERTEX_LIGHTING + shOutput(float4, lightResult) + shOutput(float3, directionalResult) #endif SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; -#if LIGHTING - normalPassthrough = normal.xyz; -#endif #ifdef NEED_DEPTH + + +#if VIEWPROJ_FIX + float4x4 vpFixed = vpMatrix; +#if !SH_GLSL + vpFixed[2] = vpRow2Fix; +#else + vpFixed[0][2] = vpRow2Fix.x; + vpFixed[1][2] = vpRow2Fix.y; + vpFixed[2][2] = vpRow2Fix.z; + vpFixed[3][2] = vpRow2Fix.w; +#endif + + float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); + + depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z; +#else depthPassthrough = shOutputPosition.z; #endif -#if LIGHTING - objSpacePositionPassthrough = shInputPosition.xyz; #endif -#if HAS_VERTEXCOLOR - colourPassthrough = colour; -#endif + objSpacePositionPassthrough = shInputPosition.xyz; #if SHADOWS lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @@ -86,6 +127,51 @@ @shForeach(3) lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos); @shEndForeach +#endif + + +#if VERTEX_LIGHTING + float3 lightDir; + float d; + lightResult = float4(0,0,0,1); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + +#if VERTEXCOLOR_MODE == 2 + lightResult.xyz += colour.xyz * lightDiffuse[@shIterator].xyz + * (1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normalize(normal.xyz), normalize(lightDir)), 0); +#else + lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz + * (1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normalize(normal.xyz), normalize(lightDir)), 0); +#endif + +#if @shIterator == 0 + directionalResult = lightResult.xyz; +#endif + + @shEndForeach + + +#if VERTEXCOLOR_MODE == 2 + lightResult.xyz += lightAmbient.xyz * colour.xyz + materialEmissive.xyz; + lightResult.a *= colour.a; +#endif +#if VERTEXCOLOR_MODE == 1 + lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colour.xyz; +#endif +#if VERTEXCOLOR_MODE == 0 + lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; +#endif + +#if VERTEXCOLOR_MODE != 2 + lightResult.a *= materialDiffuse.a; +#endif + #endif } @@ -100,45 +186,18 @@ SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) -#if MRT - shDeclareMrtOutput(1) -#endif #ifdef NEED_DEPTH shInput(float, depthPassthrough) #endif -#if MRT - shUniform(float, far) @shAutoConstant(far, far_clip_distance) -#endif - - shUniform(float, gammaCorrection) @shSharedParameter(gammaCorrection, gammaCorrection) - -#if LIGHTING - shInput(float3, normalPassthrough) shInput(float3, objSpacePositionPassthrough) - shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - #if !HAS_VERTEXCOLOR - shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) - #endif - shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - @shForeach(@shGlobalSettingString(num_lights)) - shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) - shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) - shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) - @shEndForeach -#endif #if FOG shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) #endif -#if HAS_VERTEXCOLOR - shInput(float4, colourPassthrough) -#endif - #if SHADOWS shInput(float4, lightSpacePos0) shSampler2D(shadowMap0) @@ -163,38 +222,19 @@ #endif #if UNDERWATER - - shUniform(float, waterLevel) @shSharedParameter(waterLevel) - - shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) - - shSampler2D(causticMap) - - shUniform(float, waterTimer) @shSharedParameter(waterTimer) - shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + shUniform(float, waterLevel) @shSharedParameter(waterLevel) shUniform(float, waterEnabled) @shSharedParameter(waterEnabled) - - shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) +#endif + +#if VERTEX_LIGHTING + shInput(float4, lightResult) + shInput(float3, directionalResult) #endif SH_START_PROGRAM { shOutputColour(0) = shSample(diffuseMap, UV); - shOutputColour(0).xyz = gammaCorrectRead(shOutputColour(0).xyz); - -#if LIGHTING - float3 normal = normalize(normalPassthrough); - float3 lightDir; - float3 diffuse = float3(0,0,0); - float d; - -#if HAS_VERTEXCOLOR - // ambient vertex colour tracking, FFP behaviour - float3 ambient = colourPassthrough.xyz * lightAmbient.xyz; -#else - float3 ambient = materialAmbient.xyz * lightAmbient.xyz; -#endif - + // shadows only for the first (directional) light #if SHADOWS float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); @@ -215,100 +255,34 @@ - float3 caustics = float3(1,1,1); - #if (UNDERWATER) || (FOG) float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; #endif #if UNDERWATER - float3 waterEyePos = float3(1,1,1); - // NOTE: this calculation would be wrong for non-uniform scaling - float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); - caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); - if (worldPos.y >= waterLevel || waterEnabled != 1) - caustics = float3(1,1,1); + float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif - - @shForeach(@shGlobalSettingString(num_lights)) - - /// \todo use the array auto params for lights, and use a real for-loop with auto param "light_count" iterations - lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); - d = length(lightDir); - - lightDir = normalize(lightDir); - -#if @shIterator == 0 - - #if (SHADOWS || SHADOWS_PSSM) - diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; - - #else - diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; - - #endif - +#if SHADOWS + shOutputColour(0) *= (lightResult - float4(directionalResult * (1.0-shadow),0)); #else - diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); -#endif - - @shEndForeach - - shOutputColour(0).xyz *= (ambient + diffuse + materialEmissive.xyz); -#endif - - -#if HAS_VERTEXCOLOR && !LIGHTING - shOutputColour(0).xyz *= colourPassthrough.xyz; + shOutputColour(0) *= lightResult; #endif #if FOG - float fogValue = shSaturate((length(cameraPos.xyz-worldPos) - fogParams.y) * fogParams.w); + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - #if UNDERWATER - // regular fog only if fragment is above water - if (worldPos.y > waterLevel) - #endif - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, gammaCorrectRead(fogColour), fogValue); + +#if UNDERWATER + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY)); +#else + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); +#endif + #endif // prevent negative colour output (for example with negative lights) shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); - -#if UNDERWATER - float fogAmount = (cameraPos.y > waterLevel) - ? shSaturate(length(waterEyePos-worldPos) / VISIBILITY) - : shSaturate(length(cameraPos.xyz-worldPos)/ VISIBILITY); - - float3 eyeVec = normalize(cameraPos.xyz-worldPos); - - float waterSunGradient = dot(eyeVec, -normalize(lightDirectionWS0.xyz)); - waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); - float3 waterSunColour = gammaCorrectRead(float3(0.0,1.0,0.85)) *waterSunGradient * 0.5; - - float waterGradient = dot(eyeVec, float3(0.0,-1.0,0.0)); - waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); - float3 watercolour = ( gammaCorrectRead(float3(0.0078, 0.5176, 0.700))+waterSunColour)*waterGradient*2.0; - watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); - watercolour = (cameraPos.y <= waterLevel) ? watercolour : watercolour*0.3; - - - float darkness = VISIBILITY*2.0; - darkness = clamp((waterEyePos.y - waterLevel + darkness)/darkness,0.2,1.0); - watercolour *= darkness; - - float isUnderwater = (worldPos.y < waterLevel) ? 1.0 : 0.0; - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater * waterEnabled); -#endif - - shOutputColour(0).xyz = gammaCorrectOutput(shOutputColour(0).xyz); - -#if MRT - shOutputColour(1) = float4(depthPassthrough / far,1,1,1); -#endif - } #endif diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration index 2f84680f0..b953a9131 100644 --- a/files/materials/openmw.configuration +++ b/files/materials/openmw.configuration @@ -2,14 +2,18 @@ configuration water_reflection { shadows false shadows_pssm false - mrt_output false + viewproj_fix true +} + +configuration water_refraction +{ + viewproj_fix true + render_refraction true } configuration local_map { fog false - mrt_output false - lighting false shadows false shadows_pssm false simple_water true diff --git a/files/materials/quad.mat b/files/materials/quad.mat index afb7f5111..77a2c0c34 100644 --- a/files/materials/quad.mat +++ b/files/materials/quad.mat @@ -4,7 +4,7 @@ material quad pass { - vertex_program quad_vertex + vertex_program transform_vertex fragment_program quad_fragment depth_write $depth_write @@ -20,16 +20,3 @@ material quad_noDepthWrite parent quad depth_write off } - -material openmw_viewport_init -{ - pass - { - vertex_program viewport_init_vertex - fragment_program viewport_init_fragment - - depth_write off - depth_check off - scene_blend add - } -} diff --git a/files/materials/quad.shaderset b/files/materials/quad.shaderset index c61497503..71fd82da4 100644 --- a/files/materials/quad.shaderset +++ b/files/materials/quad.shaderset @@ -1,4 +1,4 @@ -shader_set quad_vertex +shader_set transform_vertex { source quad.shader type vertex @@ -13,19 +13,3 @@ shader_set quad_fragment profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 profiles_hlsl ps_2_0 } - -shader_set viewport_init_vertex -{ - source quad2.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set viewport_init_fragment -{ - source quad2.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/quad2.shader b/files/materials/quad2.shader deleted file mode 100644 index e54d83ef4..000000000 --- a/files/materials/quad2.shader +++ /dev/null @@ -1,23 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - } - -#else - - SH_BEGIN_PROGRAM - shUniform(float3, viewportBackground) @shSharedParameter(viewportBackground) - shDeclareMrtOutput(1) - SH_START_PROGRAM - { - shOutputColour(0) = float4(viewportBackground, 1); - shOutputColour(1) = float4(1,1,1,1); - } - -#endif diff --git a/files/materials/selection.mat b/files/materials/selection.mat index a76dd7179..2cb92f884 100644 --- a/files/materials/selection.mat +++ b/files/materials/selection.mat @@ -1,5 +1,6 @@ material SelectionColour { + allow_fixed_function false pass { vertex_program selection_vertex diff --git a/files/materials/sky.mat b/files/materials/sky.mat index 4af90a170..e50aa51d8 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -59,7 +59,6 @@ material openmw_atmosphere polygon_mode_overrideable off - scene_blend alpha_blend depth_write off } } diff --git a/files/materials/stars.shader b/files/materials/stars.shader index 5a55d171e..fea007424 100644 --- a/files/materials/stars.shader +++ b/files/materials/stars.shader @@ -1,7 +1,5 @@ #include "core.h" -#define MRT @shGlobalSettingBool(mrt_output) - #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM @@ -22,9 +20,6 @@ #else SH_BEGIN_PROGRAM -#if MRT - shDeclareMrtOutput(1) -#endif shInput(float2, UV) shInput(float, fade) @@ -36,11 +31,6 @@ SH_START_PROGRAM { shOutputColour(0) = shSample(diffuseMap, UV) * float4(1,1,1, nightFade * fade); - - -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif } #endif diff --git a/files/materials/sun.shader b/files/materials/sun.shader index 45cd2f24b..7954f417c 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -1,8 +1,5 @@ #include "core.h" -#define MRT @shGlobalSettingBool(mrt_output) - - #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM @@ -21,19 +18,12 @@ SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) -#if MRT - shDeclareMrtOutput(1) -#endif shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) //shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) SH_START_PROGRAM { shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * shSample(diffuseMap, UV); - -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif } #endif diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index dee733263..c73b582f8 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -3,12 +3,9 @@ #define IS_FIRST_PASS 1 #define FOG @shGlobalSettingBool(fog) -#define MRT @shGlobalSettingBool(mrt_output) -#define LIGHTING @shGlobalSettingBool(lighting) - -#define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) -#define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) +#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) +#define SHADOWS @shGlobalSettingBool(shadows) #if SHADOWS || SHADOWS_PSSM #include "shadows.h" @@ -18,11 +15,13 @@ #define NUM_LAYERS @shPropertyString(num_layers) -#if MRT || FOG || SHADOWS_PSSM +#if FOG || SHADOWS_PSSM #define NEED_DEPTH 1 #endif -#define UNDERWATER @shGlobalSettingBool(underwater_effects) && LIGHTING +#define UNDERWATER @shGlobalSettingBool(render_refraction) + +#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) #if NEED_DEPTH @@ -31,9 +30,7 @@ @shAllocatePassthrough(2, UV) -#if LIGHTING -@shAllocatePassthrough(3, objSpacePosition) -#endif +@shAllocatePassthrough(3, worldPos) #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) @@ -52,6 +49,10 @@ shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) shUniform(float4x4, viewProjMatrix) @shAutoConstant(viewProjMatrix, viewproj_matrix) +#if VIEWPROJ_FIX + shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) +#endif + shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) shVertexInput(float2, uv0) @@ -88,21 +89,37 @@ float toMorph = -min(0, sign(uv1.y - lodMorph.y)); // morph - // this assumes XZ terrain alignment - worldPos.y += uv1.x * toMorph * lodMorph.x; + // this assumes XY terrain alignment + worldPos.z += uv1.x * toMorph * lodMorph.x; shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); #if NEED_DEPTH +#if VIEWPROJ_FIX + float4x4 vpFixed = viewProjMatrix; +#if !SH_GLSL + vpFixed[2] = vpRow2Fix; +#else + vpFixed[0][2] = vpRow2Fix.x; + vpFixed[1][2] = vpRow2Fix.y; + vpFixed[2][2] = vpRow2Fix.z; + vpFixed[3][2] = vpRow2Fix.w; +#endif + + float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); + + float depth = shMatrixMult(fixedWVP, shInputPosition).z; + @shPassthroughAssign(depth, depth); +#else @shPassthroughAssign(depth, shOutputPosition.z); +#endif + #endif @shPassthroughAssign(UV, uv0); -#if LIGHTING - @shPassthroughAssign(objSpacePosition, shInputPosition.xyz); -#endif + @shPassthroughAssign(worldPos, worldPos.xyz); #if SHADOWS float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @@ -137,8 +154,6 @@ shSampler2D(normalMap) // global normal map - shUniform(float, gammaCorrection) @shSharedParameter(gammaCorrection, gammaCorrection) - @shForeach(@shPropertyString(num_blendmaps)) shSampler2D(blendMap@shIterator) @@ -154,22 +169,13 @@ #endif @shPassthroughFragmentInputs - -#if MRT - shDeclareMrtOutput(1) - shUniform(float, far) @shAutoConstant(far, far_clip_distance) -#endif - -#if LIGHTING shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) @shForeach(@shGlobalSettingString(terrain_num_lights)) - shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) + shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position, @shIterator) shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) @shEndForeach -#endif - #if SHADOWS shSampler2D(shadowMap0) @@ -194,14 +200,6 @@ #if UNDERWATER shUniform(float, waterLevel) @shSharedParameter(waterLevel) - shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) - - shSampler2D(causticMap) - - shUniform(float, waterTimer) @shSharedParameter(waterTimer) - shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) - - shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #endif @@ -214,32 +212,14 @@ float2 UV = @shPassthroughReceive(UV); -#if LIGHTING - float3 objSpacePosition = @shPassthroughReceive(objSpacePosition); + float3 worldPos = @shPassthroughReceive(worldPos); float3 normal = shSample(normalMap, UV).rgb * 2 - 1; normal = normalize(normal); -#endif - - float3 caustics = float3(1,1,1); -#if (UNDERWATER) || (FOG) - float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)).xyz; -#endif - #if UNDERWATER - - float3 waterEyePos = float3(1,1,1); - // NOTE: this calculation would be wrong for non-uniform scaling - float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); - caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); - if (worldPos.y >= waterLevel) - caustics = float3(1,1,1); - - - + float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif @@ -254,9 +234,9 @@ #if IS_FIRST_PASS == 1 && @shIterator == 0 // first layer of first pass doesn't need a blend map - albedo = gammaCorrectRead(shSample(diffuseMap0, UV * 10).rgb); + albedo = shSample(diffuseMap0, UV * 10).rgb; #else - albedo = shLerp(albedo, gammaCorrectRead(shSample(diffuseMap@shIterator, UV * 10).rgb), blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, UV * 10).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); #endif @shEndForeach @@ -276,7 +256,6 @@ // Lighting -#if LIGHTING // shadows only for the first (directional) light #if SHADOWS float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); @@ -308,7 +287,7 @@ @shForeach(@shGlobalSettingString(terrain_num_lights)) - lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePosition.xyz * lightPosObjSpace@shIterator.w); + lightDir = lightPosObjSpace@shIterator.xyz - (worldPos.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); @@ -317,10 +296,10 @@ #if @shIterator == 0 #if (SHADOWS || SHADOWS_PSSM) - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; #else - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); #endif @@ -330,57 +309,22 @@ @shEndForeach - shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); -#endif - + shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); #if FOG - float fogValue = shSaturate((length(cameraPos.xyz-worldPos) - fogParams.y) * fogParams.w); + float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); #if UNDERWATER - // regular fog only if fragment is above water - if (worldPos.y > waterLevel) + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY)); + #else + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, gammaCorrectRead(fogColour), fogValue); #endif // prevent negative colour output (for example with negative lights) shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); - -#if UNDERWATER - float fogAmount = (cameraPos.y > waterLevel) - ? shSaturate(length(waterEyePos-worldPos) / VISIBILITY) - : shSaturate(length(cameraPos.xyz-worldPos)/ VISIBILITY); - - float3 eyeVec = normalize(cameraPos.xyz-worldPos); - - float waterSunGradient = dot(eyeVec, -normalize(lightDirectionWS0.xyz)); - waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); - float3 waterSunColour = gammaCorrectRead(float3(0.0,1.0,0.85))*waterSunGradient * 0.5; - - float waterGradient = dot(eyeVec, float3(0.0,-1.0,0.0)); - waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); - float3 watercolour = (gammaCorrectRead(float3(0.0078, 0.5176, 0.700))+waterSunColour)*waterGradient*2.0; - float3 waterext = gammaCorrectRead(float3(0.6, 0.9, 1.0));//water extinction - watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); - watercolour = (cameraPos.y <= waterLevel) ? watercolour : watercolour*0.3; - - - float darkness = VISIBILITY*2.0; - darkness = clamp((waterEyePos.y - waterLevel + darkness)/darkness,0.2,1.0); - watercolour *= darkness; - - float isUnderwater = (worldPos.y < waterLevel) ? 1.0 : 0.0; - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater); -#endif - - shOutputColour(0).xyz = gammaCorrectOutput(shOutputColour(0).xyz); - -#if MRT - shOutputColour(1) = float4(depth / far,1,1,1); -#endif } #endif diff --git a/files/materials/underwater.h b/files/materials/underwater.h index 18052a98d..8474f299d 100644 --- a/files/materials/underwater.h +++ b/files/materials/underwater.h @@ -1,4 +1,6 @@ -#define VISIBILITY 1500.0 // how far you can look through water +#define UNDERWATER_COLOUR float3(0.18039, 0.23137, 0.25490) + +#define VISIBILITY 1000.0 // how far you can look through water #define BIG_WAVES_X 0.3 // strength of big waves #define BIG_WAVES_Y 0.3 @@ -79,9 +81,9 @@ float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) { - float waterDepth = shSaturate((waterEyePos.y - worldPos.y) / 50.0); + float waterDepth = shSaturate((waterEyePos.z - worldPos.z) / 50.0); - float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,1,0), waterLevel); + float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,0,1), waterLevel); ///\ todo clean this up float causticdepth = length(causticPos-worldPos.xyz); @@ -91,20 +93,21 @@ float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, // NOTE: the original shader calculated a tangent space basis here, // but using only the world normal is cheaper and i couldn't see a visual difference // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information - float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xzy * 2 - 1; + float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xyz * 2 - 1; + causticNorm = float3(causticNorm.x, causticNorm.y, -causticNorm.z); //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); - float causticR = 1.0-perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticR = 1.0-perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; /// \todo sunFade // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; float3 caustics = clamp(pow(float3(causticR,causticR,causticR)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; - float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticG = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticB = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)))*NdotL*causticdepth; diff --git a/files/materials/water.mat b/files/materials/water.mat index dcea5a0d0..0ec71d2df 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -1,8 +1,10 @@ material Water { + allow_fixed_function false + pass { - emissive 0.6 0.7 1.0 + emissive 1.0 1.0 1.0 ambient 0 0 0 diffuse 0 0 0 1 specular 0 0 0 32 @@ -11,6 +13,9 @@ material Water fragment_program water_fragment cull_hardware none + + scene_blend alpha_blend + depth_write off texture_unit reflectionMap { @@ -20,7 +25,7 @@ material Water texture_unit refractionMap { - texture_alias WaterRefraction + direct_texture WaterRefraction tex_address_mode clamp } @@ -32,10 +37,16 @@ material Water texture_unit normalMap { - direct_texture water_nm.png + texture water_nm.png 5 } - - + + texture_unit rippleNormalMap + { + direct_texture RippleNormal + tex_address_mode border + tex_border_colour 0.5 0.5 1.0 + } + // for simple_water texture_unit animatedTexture { @@ -45,11 +56,3 @@ material Water } } } - - -material Underwater_Dome -{ - parent openmw_objects_base - - depth_write off -} diff --git a/files/materials/water.shader b/files/materials/water.shader index 6bd277eab..793cdc95e 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -5,11 +5,7 @@ #if SIMPLE_WATER - // --------------------------------------- SIMPLE WATER --------------------------------------------------- - - - #define MRT @shGlobalSettingBool(mrt_output) - + // --------------------------------------- SIMPLE WATER --------------------------------------------------- #ifdef SH_VERTEX_SHADER @@ -32,9 +28,6 @@ shSampler2D(animatedTexture) shInput(float2, UV) shInput(float, depth) -#if MRT - shDeclareMrtOutput(1) -#endif shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) @@ -42,15 +35,11 @@ SH_START_PROGRAM { - shOutputColour(0).xyz = shSample(animatedTexture, UV * 15).xyz * float3(0.6, 0.7, 1.0); + shOutputColour(0).xyz = shSample(animatedTexture, UV * 15).xyz * float3(1.0, 1.0, 1.0); shOutputColour(0).w = 0.7; float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); - -#if MRT - shOutputColour(1) = float4(1,1,1,1); -#endif } #endif @@ -61,7 +50,8 @@ // Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) - +#define RIPPLES 1 +#define REFRACTION @shGlobalSettingBool(refraction) #ifdef SH_VERTEX_SHADER @@ -122,10 +112,10 @@ #define REFL_BUMP 0.08 // reflection distortion amount #define REFR_BUMP 0.06 // refraction distortion amount - #define SCATTER_AMOUNT 3.0 // amount of sunlight scattering - #define SCATTER_COLOUR gammaCorrectRead(float3(0.0,1.0,0.95)) // colour of sunlight scattering + #define SCATTER_AMOUNT 0.3 // amount of sunlight scattering + #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering - #define SUN_EXT gammaCorrectRead(float3(0.45, 0.55, 0.68)) //sunlight extinction + #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction #define SPEC_HARDNESS 256 // specular highlights hardness @@ -159,13 +149,25 @@ shInput(float3, screenCoordsPassthrough) shInput(float4, position) shInput(float, depthPassthrough) + + #if RIPPLES + shUniform(float3, rippleCenter) @shSharedParameter(rippleCenter, rippleCenter) + shUniform(float, rippleAreaLength) @shSharedParameter(rippleAreaLength, rippleAreaLength) + #endif shUniform(float, far) @shAutoConstant(far, far_clip_distance) shSampler2D(reflectionMap) +#if REFRACTION shSampler2D(refractionMap) +#endif shSampler2D(depthMap) shSampler2D(normalMap) + + #if RIPPLES + shSampler2D(rippleNormalMap) + shUniform(float4x4, wMat) @shAutoConstant(wMat, world_matrix) + #endif shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #define WIND_SPEED windDir_windSpeed.z @@ -176,9 +178,6 @@ shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0) shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0) - - shUniform(float, gammaCorrection) @shSharedParameter(gammaCorrection, gammaCorrection) - shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping) @@ -191,13 +190,9 @@ SH_START_PROGRAM { - float2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; screenCoords.y = (1-shSaturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; - float depth = shSample(depthMap, screenCoords).x * far - depthPassthrough; - float shoreFade = shSaturate(depth / 50.0); - float2 nCoord = float2(0,0); nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); @@ -219,92 +214,75 @@ float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + - normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y).xzy; - - normal = normalize(float3(normal.x * BUMP, normal.y, normal.z * BUMP)); - + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + + float4 worldPosition = shMatrixMult(wMat, float4(position.xyz, 1)); + float2 relPos = (worldPosition.xy - rippleCenter.xy) / rippleAreaLength + 0.5; + float3 normal_ripple = normalize(shSample(rippleNormalMap, relPos.xy).xyz * 2 - 1); + + //normal = normalize(normal + normal_ripple); + normal = normalize(float3(normal.x * BUMP + normal_ripple.x, normal.y * BUMP + normal_ripple.y, normal.z)); + normal = float3(normal.x, normal.y, -normal.z); + // normal for sunlight scattering float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + - normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xzy; - lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y, lNormal.z * BUMP)); - + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz; + lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z)); + lNormal = float3(lNormal.x, lNormal.y, -lNormal.z); + float3 lVec = normalize(sunPosition.xyz); float3 vVec = normalize(position.xyz - cameraPos.xyz); - float isUnderwater = (cameraPos.y > 0) ? 0.0 : 1.0; + float isUnderwater = (cameraPos.z > 0) ? 0.0 : 1.0; // sunlight scattering - float3 pNormal = float3(0,1,0); + float3 pNormal = float3(0,0,1); float3 lR = reflect(lVec, lNormal); float3 llR = reflect(lVec, pNormal); float s = shSaturate(dot(lR, vVec)*2.0-1.2); float lightScatter = shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); - float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*gammaCorrectRead(float3(1.0,0.4,0.0)), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*float3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); // fresnel - float ior = (cameraPos.y>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air + float ior = (cameraPos.z>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air float fresnel = fresnel_dielectric(-vVec, normal, ior); fresnel = shSaturate(fresnel); // reflection - float3 reflection = gammaCorrectRead(shSample(reflectionMap, screenCoords+(normal.xz*REFL_BUMP)).rgb); + float3 reflection = shSample(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; // refraction float3 R = reflect(vVec, normal); - - // check the depth at the refracted coords, and don't do any normal distortion for the refraction if the object to refract - // is actually above the water (objectDepth < waterDepth) - // this solves silhouettes around objects above the water - float refractDepth = shSample(depthMap, screenCoords-(shoreFade * normal.xz*REFR_BUMP)).x * far - depthPassthrough; - float doRefraction = (refractDepth < 0) ? 0.f : 1.f; - - float3 refraction = gammaCorrectRead(shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP * doRefraction))*1.0).rgb); + +#if REFRACTION + float3 refraction = shSample(refractionMap, (screenCoords-(normal.xy*REFR_BUMP))*1.0).rgb; // brighten up the refraction underwater - refraction = (cameraPos.y < 0) ? shSaturate(refraction * 1.5) : refraction; - + refraction = (cameraPos.z < 0) ? shSaturate(refraction * 1.5) : refraction; +#endif + // specular float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS); +#if REFRACTION shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; - - // smooth transition to shore (above water only) - shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, refraction, (1-shoreFade) * (1-isUnderwater)); - +#else + shOutputColour(0).xyz = shLerp(reflection, float3(0.18039, 0.23137, 0.25490), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; +#endif // fog - if (isUnderwater == 1) - { - float waterSunGradient = dot(-vVec, -lVec); - waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); - float3 waterSunColour = gammaCorrectRead(float3(0.0,1.0,0.85))*waterSunGradient * 0.5; - - float waterGradient = dot(-vVec, float3(0.0,-1.0,0.0)); - waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); - float3 watercolour = (gammaCorrectRead(float3(0.0078, 0.5176, 0.700))+waterSunColour)*waterGradient*2.0; - float3 waterext = gammaCorrectRead(float3(0.6, 0.9, 1.0));//water extinction - watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); - - float darkness = VISIBILITY*2.0; - darkness = clamp((cameraPos.y+darkness)/darkness,0.2,1.0); - - - float fog = shSaturate(length(cameraPos.xyz-position.xyz) / VISIBILITY); - shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, watercolour * darkness, shSaturate(fog / waterext)); - } - else - { - float fogValue = shSaturate((length(cameraPos.xyz-position.xyz) - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, gammaCorrectRead(fogColor), fogValue); - } + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); - shOutputColour(0).xyz = gammaCorrectOutput(shOutputColour(0).xyz); - - shOutputColour(0).w = 1; +#if REFRACTION + shOutputColour(0).w = 1; +#else + shOutputColour(0).w = shSaturate(fresnel*2 + specular); +#endif } #endif diff --git a/files/materials/watersim.mat b/files/materials/watersim.mat new file mode 100644 index 000000000..b58b1a851 --- /dev/null +++ b/files/materials/watersim.mat @@ -0,0 +1,59 @@ +material HeightmapSimulation +{ + allow_fixed_function false + pass + { + depth_check off + depth_write off + vertex_program transform_vertex + fragment_program watersim_fragment + + texture_unit heightPrevSampler + { + tex_address_mode border + tex_border_colour 0 0 0 + texture_alias Heightmap0 + } + texture_unit heightCurrentSampler + { + tex_address_mode border + tex_border_colour 0 0 0 + texture_alias Heightmap1 + } + } +} + +material HeightToNormalMap +{ + allow_fixed_function false + pass + { + depth_check off + depth_write off + vertex_program transform_vertex + fragment_program height_to_normal_fragment + + texture_unit heightCurrentSampler + { + texture_alias Heightmap2 + } + } +} + +material AddImpulse +{ + allow_fixed_function false + pass + { + depth_check off + depth_write off + scene_blend alpha_blend + vertex_program transform_vertex + fragment_program add_impulse_fragment + + texture_unit alphaMap + { + texture circle.png + } + } +} diff --git a/files/materials/watersim.shaderset b/files/materials/watersim.shaderset new file mode 100644 index 000000000..ea512e25f --- /dev/null +++ b/files/materials/watersim.shaderset @@ -0,0 +1,31 @@ +shader_set transform_vertex +{ + source quad.shader + type vertex + profiles_cg vs_2_0 vp40 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set watersim_fragment +{ + source watersim_heightmap.shader + type fragment + profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 + profiles_hlsl ps_3_0 ps_2_0 +} + +shader_set height_to_normal_fragment +{ + source watersim_heighttonormal.shader + type fragment + profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 + profiles_hlsl ps_3_0 ps_2_0 +} + +shader_set add_impulse_fragment +{ + source watersim_addimpulse.shader + type fragment + profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 + profiles_hlsl ps_3_0 ps_2_0 +} diff --git a/files/materials/watersim_addimpulse.shader b/files/materials/watersim_addimpulse.shader new file mode 100644 index 000000000..3ca4192cd --- /dev/null +++ b/files/materials/watersim_addimpulse.shader @@ -0,0 +1,12 @@ +#include "core.h" +#include "watersim_common.h" + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shSampler2D(alphaMap) + + SH_START_PROGRAM + { + shOutputColour(0) = EncodeHeightmap(1.0); + shOutputColour(0).a = shSample (alphaMap, UV.xy).a; + } diff --git a/files/materials/watersim_common.h b/files/materials/watersim_common.h new file mode 100644 index 000000000..aa7a636a0 --- /dev/null +++ b/files/materials/watersim_common.h @@ -0,0 +1,25 @@ +float DecodeHeightmap(float4 heightmap) +{ + float4 table = float4(1.0, -1.0, 0.0, 0.0); + return dot(heightmap, table); +} + +float DecodeHeightmap(shTexture2D HeightmapSampler, float2 texcoord) +{ + float4 heightmap = shSample(HeightmapSampler, texcoord); + return DecodeHeightmap(heightmap); +} + +float4 EncodeHeightmap(float fHeight) +{ + float h = fHeight; + float positive = fHeight > 0.0 ? fHeight : 0.0; + float negative = fHeight < 0.0 ? -fHeight : 0.0; + + float4 color = float4(0,0,0,0); + + color.r = positive; + color.g = negative; + + return color; +} diff --git a/files/materials/watersim_heightmap.shader b/files/materials/watersim_heightmap.shader new file mode 100644 index 000000000..50d375efe --- /dev/null +++ b/files/materials/watersim_heightmap.shader @@ -0,0 +1,42 @@ +#include "core.h" + +#define DAMPING 0.95 + +#include "watersim_common.h" + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shSampler2D(heightPrevSampler) + shSampler2D(heightCurrentSampler) + shUniform(float3, previousFrameOffset) @shSharedParameter(previousFrameOffset, previousFrameOffset) + shUniform(float3, currentFrameOffset) @shSharedParameter(currentFrameOffset, currentFrameOffset) + shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize) + + SH_START_PROGRAM + { + const float3 offset[4] = float3[4]( + float3(-1.0, 0.0, 0.25), + float3( 1.0, 0.0, 0.25), + float3( 0.0,-1.0, 0.25), + float3( 0.0, 1.0, 0.25) + ); + + float fHeightPrev = DecodeHeightmap(heightPrevSampler, UV.xy + previousFrameOffset.xy + currentFrameOffset.xy); + + float fNeighCurrent = 0; + for ( int i=0; i<4; i++ ) + { + float2 vTexcoord = UV + currentFrameOffset.xy + offset[i].xy * rippleTextureSize.xy; + fNeighCurrent += (DecodeHeightmap(heightCurrentSampler, vTexcoord) * offset[i].z); + } + + float fHeight = fNeighCurrent * 2.0 - fHeightPrev; + + fHeight *= DAMPING; + + shOutputColour(0) = EncodeHeightmap(fHeight); + } + + + + diff --git a/files/materials/watersim_heighttonormal.shader b/files/materials/watersim_heighttonormal.shader new file mode 100644 index 000000000..5402b6bb5 --- /dev/null +++ b/files/materials/watersim_heighttonormal.shader @@ -0,0 +1,27 @@ +#include "core.h" +#include "watersim_common.h" + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shSampler2D(heightCurrentSampler) + shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize) + + SH_START_PROGRAM + { + float2 offset[4] = float2[4] ( + vec2(-1.0, 0.0), + vec2( 1.0, 0.0), + vec2( 0.0,-1.0), + vec2( 0.0, 1.0) + ); + + float fHeightL = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[0]*rippleTextureSize.xy); + float fHeightR = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[1]*rippleTextureSize.xy); + float fHeightT = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[2]*rippleTextureSize.xy); + float fHeightB = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[3]*rippleTextureSize.xy); + + float3 n = float3(fHeightB - fHeightT, fHeightR - fHeightL, 1.0); + float3 normal = (n + 1.0) * 0.5; + + shOutputColour(0) = float4(normal.rgb, 1.0); + } diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 562668a90..beace5b81 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -4,14 +4,11 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) set(MYGUI_FILES - atlas1.cfg - mainmenu.cfg bigbars.png black.png core.skin core.xml EBGaramond-Regular.ttf - mwgui.png Obliviontt.zip openmw_alchemy_window.layout openmw_book.layout @@ -82,7 +79,7 @@ set(MYGUI_FILES openmw_travel_window.layout openmw_persuasion_dialog.layout smallbars.png - VeraMono.ttf + DejaVuLGCSansMono.ttf markers.png ) diff --git a/files/mygui/DejaVuLGCSansMono.ttf b/files/mygui/DejaVuLGCSansMono.ttf new file mode 100644 index 000000000..80c45b73e Binary files /dev/null and b/files/mygui/DejaVuLGCSansMono.ttf differ diff --git a/files/mygui/VeraMono.ttf b/files/mygui/VeraMono.ttf deleted file mode 100644 index 139f0b431..000000000 Binary files a/files/mygui/VeraMono.ttf and /dev/null differ diff --git a/files/mygui/atlas1.cfg b/files/mygui/atlas1.cfg deleted file mode 100644 index d1e05e041..000000000 --- a/files/mygui/atlas1.cfg +++ /dev/null @@ -1,51 +0,0 @@ -[settings] - size_x = 512 - size_y = 512 - -[tx_menubook_close_idle.dds] - x = 0 - y = 0 - -[tx_menubook_close_over.dds] - x = 128 - y = 0 - -[tx_menubook_close_pressed.dds] - x = 256 - y = 0 - -[tx_menubook_take_idle.dds] - x = 384 - y = 0 - -[tx_menubook_take_over.dds] - x = 0 - y = 32 - -[tx_menubook_take_pressed.dds] - x = 128 - y = 32 - -[tx_menubook_next_idle.dds] - x = 256 - y = 32 - -[tx_menubook_next_over.dds] - x = 384 - y = 32 - -[tx_menubook_next_pressed.dds] - x = 0 - y = 64 - -[tx_menubook_prev_idle.dds] - x = 128 - y = 64 - -[tx_menubook_prev_over.dds] - x = 256 - y = 64 - -[tx_menubook_prev_pressed.dds] - x = 384 - y = 64 diff --git a/files/mygui/mainmenu.cfg b/files/mygui/mainmenu.cfg deleted file mode 100644 index 7aaf8c1c7..000000000 --- a/files/mygui/mainmenu.cfg +++ /dev/null @@ -1,95 +0,0 @@ -[settings] - size_x = 512 - size_y = 512 - - -[menu_newgame.dds] - x = 0 - y = 0 - -[menu_newgame_pressed.dds] - x = 128 - y = 0 - -[menu_newgame_over.dds] - x = 256 - y = 0 - - -[menu_loadgame.dds] - x = 384 - y = 0 - -[menu_loadgame_pressed.dds] - x = 0 - y = 64 - -[menu_loadgame_over.dds] - x = 128 - y = 64 - - -[menu_options.dds] - x = 256 - y = 64 - -[menu_options_pressed.dds] - x = 384 - y = 64 - -[menu_options_over.dds] - x = 0 - y = 128 - - -[menu_credits.dds] - x = 128 - y = 128 - -[menu_credits_pressed.dds] - x = 256 - y = 128 - -[menu_credits_over.dds] - x = 384 - y = 128 - - -[menu_exitgame.dds] - x = 0 - y = 192 - -[menu_exitgame_pressed.dds] - x = 128 - y = 192 - -[menu_exitgame_over.dds] - x = 256 - y = 192 - - -[menu_savegame.dds] - x = 384 - y = 192 - -[menu_savegame_pressed.dds] - x = 0 - y = 256 - -[menu_savegame_over.dds] - x = 128 - y = 256 - - -[menu_return.dds] - x = 256 - y = 256 - -[menu_return_pressed.dds] - x = 384 - y = 256 - -[menu_return_over.dds] - x = 0 - y = 320 - diff --git a/files/mygui/mwgui.png b/files/mygui/mwgui.png deleted file mode 100644 index 318f16e41..000000000 Binary files a/files/mygui/mwgui.png and /dev/null differ diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index 6c708cdd3..96d1153f0 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -2,33 +2,45 @@ - + - + + - - + + + + + - - + + + + + - - + + + + - - + + + + + - + - + - - + + diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout index 11031eb4e..8823e1e76 100644 --- a/files/mygui/openmw_chargen_class_description.layout +++ b/files/mygui/openmw_chargen_class_description.layout @@ -4,6 +4,9 @@ + + + diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index a9ec9905d..4ef8da0f3 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -8,33 +8,52 @@ - + - + - - + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + + + - + @@ -42,11 +61,11 @@ - + - + diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout index 94e9458a5..452196aae 100644 --- a/files/mygui/openmw_container_window.layout +++ b/files/mygui/openmw_container_window.layout @@ -15,6 +15,10 @@ + + + + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 1271a287b..9a9da72d4 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -3,13 +3,15 @@ + + - + - + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index 02fee4b17..da21385e2 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -1,55 +1,52 @@ + + + + + + - - - - + - - - - - - - - - - - - + + + + + + + + + + + + + - + + + + - - - - - - - - - - - - - + + + diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index 45ecb63ed..cad22c064 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -31,7 +31,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml index cb5dd648f..b1446fae1 100644 --- a/files/mygui/openmw_font.xml +++ b/files/mygui/openmw_font.xml @@ -11,6 +11,7 @@ + @@ -35,7 +36,7 @@ - + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index e4c6f0765..382bc6dc9 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -61,10 +61,7 @@ - - + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index ce231e5bb..f847ca51a 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -1,78 +1,35 @@ + - - - - - - - - - - - - - - - + - - - - - - - + - - - - - - - + + + - - - - - - - - + + + - - - - - - - + - - - - - - - + - - - - - - - + + + + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index 3a60916f7..88cc5638d 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index e4c3c7e47..fdf82e4de 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -7,11 +7,15 @@ - - + + + + - - + + + + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 64435451a..6631424cc 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -1,228 +1,107 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + + + + @@ -234,81 +113,85 @@ - - - - + + + + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + - + - - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 6862702ca..1e4bba5ed 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -6,12 +6,13 @@ - + - + + - + diff --git a/files/mygui/openmw_mainmenu_skin.xml b/files/mygui/openmw_mainmenu_skin.xml index 4100a2eb7..c7f2fbce3 100644 --- a/files/mygui/openmw_mainmenu_skin.xml +++ b/files/mygui/openmw_mainmenu_skin.xml @@ -1,34 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index b5479b676..d4b87e60d 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_map_window_skin.xml b/files/mygui/openmw_map_window_skin.xml index 0c6050969..13f18c6d3 100644 --- a/files/mygui/openmw_map_window_skin.xml +++ b/files/mygui/openmw_map_window_skin.xml @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_pointer.xml b/files/mygui/openmw_pointer.xml index 42ee5d435..cf21037f8 100644 --- a/files/mygui/openmw_pointer.xml +++ b/files/mygui/openmw_pointer.xml @@ -5,26 +5,31 @@ + + - + + - + + - + + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index c4b94e28e..4666be221 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -17,6 +17,11 @@ + + + + + @@ -51,4 +56,12 @@ + + + + + + + + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 5a695515d..e47ff6386 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -17,27 +17,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_scroll.layout b/files/mygui/openmw_scroll.layout index 0f4a0be3e..6315c0241 100644 --- a/files/mygui/openmw_scroll.layout +++ b/files/mygui/openmw_scroll.layout @@ -7,12 +7,16 @@ - - + + + + - - + + + + diff --git a/files/mygui/openmw_scroll_skin.xml b/files/mygui/openmw_scroll_skin.xml index 70fad3f4b..1b94f0c29 100644 --- a/files/mygui/openmw_scroll_skin.xml +++ b/files/mygui/openmw_scroll_skin.xml @@ -2,12 +2,12 @@ - + - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 2f9b5a67f..693c5a9cb 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -15,7 +15,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -64,35 +64,35 @@ - + - + - + - + - + @@ -117,7 +117,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -157,58 +157,48 @@ - + - + - + - - - - - - - - + - - - - - - - - - - - - - - + + + + + + + + + + + - + @@ -233,7 +223,7 @@ - + @@ -241,7 +231,7 @@ - + @@ -290,9 +280,9 @@ - + - + diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index d489f41b8..18a5af352 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -1,11 +1,11 @@ - + - - + + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index c2c8a5dae..55ee7b9da 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout index d38377f98..ecc794c92 100644 --- a/files/mygui/openmw_trade_window.layout +++ b/files/mygui/openmw_trade_window.layout @@ -51,10 +51,6 @@ - - - - @@ -66,6 +62,10 @@ + + + + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index a9eb23d68..73f68e80d 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -8,6 +8,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -473,9 +585,7 @@ - - - + diff --git a/files/mygui/smallbars.png b/files/mygui/smallbars.png index a9ed572ef..f938412c2 100644 Binary files a/files/mygui/smallbars.png and b/files/mygui/smallbars.png differ diff --git a/files/opencs/opencs.png b/files/opencs/opencs.png new file mode 100644 index 000000000..dddf220a3 Binary files /dev/null and b/files/opencs/opencs.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc new file mode 100644 index 000000000..ecfab44a2 --- /dev/null +++ b/files/opencs/resources.qrc @@ -0,0 +1,5 @@ + + + opencs.png + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1768b2f5e..69aa20883 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -28,8 +28,6 @@ vsync = false # PBuffer, FBO, Copy opengl rtt mode = FBO -gamma = 2.2 - [GUI] # 1 is fully opaque menu transparency = 0.84 @@ -54,7 +52,8 @@ texture filtering = anisotropic anisotropy = 4 # Number of texture mipmaps to generate -num mipmaps = 5 +# This setting is currently ignored due to mipmap generation problems on Intel/AMD +#num mipmaps = 5 shader mode = @@ -129,11 +128,10 @@ fog end factor = 1.0 num lights = 8 [Water] -# Enable this to get fancy-looking water with reflections and refractions -# Only available if object shaders are on -# All the settings below have no effect if this is false shader = true +refraction = true + rtt size = 512 reflect terrain = true reflect statics = false @@ -141,9 +139,6 @@ reflect small statics = false reflect actors = false reflect misc = false -# Enable underwater effect. It is not resource intensive, so only disable it if you have problems. -underwater effect = true - [Sound] # Device name. Blank means default device = diff --git a/files/transparency-overrides.cfg b/files/transparency-overrides.cfg index 299792be1..65f9b477a 100644 --- a/files/transparency-overrides.cfg +++ b/files/transparency-overrides.cfg @@ -572,3 +572,52 @@ [textures\tx_velothi_glyph00.dds] alphaRejectValue = 128 + + + +# Bloodmoon + +[textures\tx_bm_holly_01.dds] + alphaRejectValue = 128 + +[textures\tx_bm_holly_snow_01.dds] + alphaRejectValue = 128 + +[textures\tx_bm_pine_04a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_pine_03a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_pine_02a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_pine_01a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_shrub_02.dds] + alphaRejectValue = 128 + +[textures\tx_bm_shrub_01.dds] + alphaRejectValue = 128 + +[textures\tx_bm_snow_pine_01a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_snow_pine_02a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_snow_pine_03a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_snow_pine_04a.dds] + alphaRejectValue = 128 + +[textures\tx_bm_deadpine_01.dds] + alphaRejectValue = 128 + +[textures\tx_bm_shrub_snow_02.dds] + alphaRejectValue = 128 + +[textures\tx_bm_s_deadpine_01.dds] + alphaRejectValue = 128 diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui new file mode 100644 index 000000000..044817fb4 --- /dev/null +++ b/files/ui/datafilespage.ui @@ -0,0 +1,125 @@ + + + DataFilesPage + + + + 0 + 0 + 520 + 256 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter: + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + false + + + + + + + + + + + Current Profile: + + + + + + + + 0 + 0 + + + + + + + + New Profile + + + &New Profile + + + true + + + + + + + Delete Profile + + + Delete Profile + + + Ctrl+D + + + true + + + + + + + + + + LineEdit + QLineEdit +

components/fileorderlist/utils/lineedit.hpp
+ + + ProfilesComboBox + QComboBox +
components/fileorderlist/utils/profilescombobox.hpp
+
+ + + + diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui new file mode 100644 index 000000000..5c330cebd --- /dev/null +++ b/files/ui/graphicspage.ui @@ -0,0 +1,142 @@ + + + GraphicsPage + + + + 0 + 0 + 332 + 297 + + + + + + + Render System + + + + + + Rendering Subsystem: + + + + + + + + + + + + + Display + + + + + + Vertical Sync + + + + + + + Full Screen + + + + + + + Anti-aliasing: + + + + + + + Resolution: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + + + 800 + + + + + + + x + + + + + + + 600 + + + + + + + + + Custom: + + + + + + + Standard: + + + true + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 61 + + + + + + + + + diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui new file mode 100644 index 000000000..4e1a7854d --- /dev/null +++ b/files/ui/mainwindow.ui @@ -0,0 +1,80 @@ + + + MainWindow + + + + 0 + 0 + 575 + 575 + + + + + 575 + 575 + + + + OpenMW Launcher + + + + :/images/openmw.png:/images/openmw.png + + + + + + + + 400 + 80 + + + + + 16777215 + 80 + + + + #iconWidget { + background-image: url(":/images/openmw-header.png"); + background-color: white; + background-repeat: no-repeat; + background-attachment: scroll; + background-position: right; +} + + + + + + + + + + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + + + + + diff --git a/files/ui/playpage.ui b/files/ui/playpage.ui new file mode 100644 index 000000000..ccd17f519 --- /dev/null +++ b/files/ui/playpage.ui @@ -0,0 +1,192 @@ + + + PlayPage + + + + + + #Scroll { + background-image: url(":/images/playpage-background.png"); + background-repeat: no-repeat; + background-position: top; +} + + + + QFrame::StyledPanel + + + QFrame::Plain + + + + 30 + + + 100 + + + 30 + + + + + #profilesComboBox { + padding: 1px 18px 1px 3px; + + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, stop:0.2 rgba(0, 0, 0, 25), stop:1 white); + border-width: 1px; + border-color: rgba(0, 0, 0, 125); + border-style: solid; + border-radius: 2px; +} + +/*QComboBox gets the "on" state when the popup is open */ +#profilesComboBox:!editable:on, #ProfilesComboBox::drop-down:editable:on { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(0, 0, 0, 75), + stop:0.1 rgba(0, 0, 0, 15), + stop:0.2 rgba(255, 255, 255, 55)); + + border: 1px solid rgba(0, 0, 0, 55); +} + +#profilesComboBox { /* shift the text when the popup opens */ + padding-top: 3px; + padding-left: 4px; + + font-size: 12pt; + font-family: "EB Garamond", "EB Garamond 08"; +} + +#profilesComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + + border-width: 1px; + border-left-width: 1px; + border-left-color: darkgray; + border-left-style: solid; /* just a single line */ + border-top-right-radius: 3px; /* same radius as the QComboBox */ + border-bottom-right-radius: 3px; +} + +#profilesComboBox::down-arrow { + image: url(":/images/down.png"); +} + +#profilesComboBox::down-arrow:on { /* shift the arrow when popup is open */ + top: 1px; + left: 1px; +} + +#profilesComboBox QAbstractItemView { + border: 2px solid lightgray; + border-radius: 5px; +} + + + + + + + + #profileLabel { + font-size: 18pt; + font-family: "EB Garamond", "EB Garamond 08"; +} + + + + Current Profile: + + + + + + + + 200 + 85 + + + + + 200 + 85 + + + + #playButton { + height: 50px; + margin-bottom: 30px; + + background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(255, 255, 255, 200), + stop:0.1 rgba(255, 255, 255, 15), + stop:0.49 rgba(255, 255, 255, 75), + stop:0.5 rgba(0, 0, 0, 0), + stop:0.9 rgba(0, 0, 0, 55), + stop:1 rgba(0, 0, 0, 100)); + + font-size: 26pt; + font-family: "EB Garamond", "EB Garamond 08"; + color: black; + + border-right: 1px solid rgba(0, 0, 0, 155); + border-left: 1px solid rgba(0, 0, 0, 55); + border-top: 1px solid rgba(0, 0, 0, 55); + border-bottom: 1px solid rgba(0, 0, 0, 155); + + border-radius: 5px; +} + +#playButton:hover { + border-bottom: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); + border-top: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); + border-right: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); + border-left: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0)); + border-width: 2px; + border-style: solid; +} + +#playButton:pressed { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgba(0, 0, 0, 75), + stop:0.1 rgba(0, 0, 0, 15), + stop:0.2 rgba(255, 255, 255, 55) + stop:0.95 rgba(255, 255, 255, 55), + stop:1 rgba(255, 255, 255, 155)); + + border: 1px solid rgba(0, 0, 0, 55); +} + + + Play + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + diff --git a/files/water/circle.png b/files/water/circle.png new file mode 100644 index 000000000..9a1cf268c Binary files /dev/null and b/files/water/circle.png differ diff --git a/files/water/underwater_dome.mesh b/files/water/underwater_dome.mesh deleted file mode 100644 index 64ca569c2..000000000 Binary files a/files/water/underwater_dome.mesh and /dev/null differ diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h index 423924eda..b20a3ff98 100644 --- a/libs/openengine/bullet/BtOgreExtras.h +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -207,7 +207,7 @@ public: mLineDrawer->setMaterial("BtOgre/DebugLines"); - mLineDrawer->setVisibilityFlags (1024); + //mLineDrawer->setVisibilityFlags (1024); } ~DebugDrawer() diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp index 6be615dfb..c20415884 100644 --- a/libs/openengine/bullet/CMotionState.cpp +++ b/libs/openengine/bullet/CMotionState.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace OEngine { namespace Physic diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index b39ba53a2..e5d7c367f 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -2,8 +2,7 @@ #include #include #include -#include "pmove.h" -#include +#include #include "CMotionState.h" #include "OgreRoot.h" #include "btKinematicCharacterController.h" @@ -14,104 +13,56 @@ #include #include -#define BIT(x) (1<<(x)) - namespace OEngine { namespace Physic { - enum collisiontypes { - COL_NOTHING = 0, //createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); Ogre::Quaternion inverse = mBoxRotation.Inverse(); mBoxRotationInverse = btQuaternion(inverse.x, inverse.y, inverse.z,inverse.w); mEngine->addRigidBody(mBody, false); //Add rigid body to dynamics world, but do not add to object map - pmove = new playerMove; - pmove->mEngine = mEngine; - btBoxShape* box = static_cast (mBody->getCollisionShape()); - if(box != NULL){ - btVector3 size = box->getHalfExtentsWithMargin(); - Ogre::Vector3 halfExtents = Ogre::Vector3(size.getX(), size.getY(), size.getZ()); - pmove->ps.halfExtents = halfExtents; - } + //mBody->setCollisionFlags(COL_NOTHING); + //mBody->setMas } PhysicActor::~PhysicActor() { - if(mBody){ + if(mBody) + { mEngine->dynamicsWorld->removeRigidBody(mBody); delete mBody; } - delete pmove; - } - - void PhysicActor::setCurrentWater(bool hasWater, int waterHeight){ - pmove->hasWater = hasWater; - if(hasWater){ - pmove->waterHeight = waterHeight; - } - } - - void PhysicActor::setGravity(float gravity) - { - pmove->ps.gravity = gravity; - } - - void PhysicActor::setSpeed(float speed) - { - pmove->ps.speed = speed; } void PhysicActor::enableCollisions(bool collision) { + if(collision && !collisionMode) mBody->translate(btVector3(0,0,-1000)); + if(!collision && collisionMode) mBody->translate(btVector3(0,0,1000)); collisionMode = collision; - if(collisionMode) - pmove->ps.move_type=PM_NORMAL; - else - pmove->ps.move_type=PM_NOCLIP; } - void PhysicActor::setJumpVelocity(float velocity) + + void PhysicActor::setPosition(const Ogre::Vector3 &pos) { - pmove->ps.jump_velocity = velocity; + if(pos != getPosition()) + mEngine->adjustRigidBody(mBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); } - bool PhysicActor::getCollisionMode() - { - return collisionMode; - } - - void PhysicActor::setMovement(signed char rightmove, signed char forwardmove, signed char upmove) - { - playerMove::playercmd& pm_ref = pmove->cmd; - pm_ref.rightmove = rightmove; - pm_ref.forwardmove = forwardmove; - pm_ref.upmove = upmove; - } - - void PhysicActor::setPmoveViewAngles(float pitch, float yaw, float roll){ - pmove->ps.viewangles.x = pitch; - pmove->ps.viewangles.y = yaw; - pmove->ps.viewangles.z = roll; - } - - - - void PhysicActor::setRotation(const Ogre::Quaternion quat) + void PhysicActor::setRotation(const Ogre::Quaternion &quat) { if(!quat.equals(getRotation(), Ogre::Radian(0))){ mEngine->adjustRigidBody(mBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation); } } + Ogre::Vector3 PhysicActor::getPosition() { btVector3 vec = mBody->getWorldTransform().getOrigin(); @@ -128,13 +79,6 @@ namespace Physic return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()); } - void PhysicActor::setPosition(const Ogre::Vector3 pos) - { - mEngine->adjustRigidBody(mBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); - btVector3 vec = mBody->getWorldTransform().getOrigin(); - pmove->ps.origin = Ogre::Vector3(vec.getX(), vec.getY(), vec.getZ()); - } - void PhysicActor::setScale(float scale){ Ogre::Vector3 position = getPosition(); Ogre::Quaternion rotation = getRotation(); @@ -148,18 +92,40 @@ namespace Physic //Create the newly scaled rigid body mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation); mEngine->addRigidBody(mBody, false); //Add rigid body to dynamics world, but do not add to object map - btBoxShape* box = static_cast (mBody->getCollisionShape()); - if(box != NULL){ - btVector3 size = box->getHalfExtentsWithMargin(); - Ogre::Vector3 halfExtents = Ogre::Vector3(size.getX(), size.getY(), size.getZ()); - pmove->ps.halfExtents = halfExtents; - } } - void PhysicActor::runPmove(){ - Pmove(pmove); - Ogre::Vector3 newpos = pmove->ps.origin; - mBody->getWorldTransform().setOrigin(btVector3(newpos.x, newpos.y, newpos.z)); + Ogre::Vector3 PhysicActor::getHalfExtents() const + { + if(mBody) + { + btBoxShape *box = static_cast(mBody->getCollisionShape()); + if(box != NULL) + { + btVector3 size = box->getHalfExtentsWithMargin(); + return Ogre::Vector3(size.getX(), size.getY(), size.getZ()); + } + } + return Ogre::Vector3(0.0f); + } + + void PhysicActor::setVerticalForce(float force) + { + verticalForce = force; + } + + float PhysicActor::getVerticalForce() const + { + return verticalForce; + } + + void PhysicActor::setOnGround(bool grounded) + { + onGround = grounded; + } + + bool PhysicActor::getOnGround() const + { + return collisionMode && onGround; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -221,11 +187,7 @@ namespace Physic { if(!isDebugCreated) { - Ogre::SceneManagerEnumerator::SceneManagerIterator iter = Ogre::Root::getSingleton().getSceneManagerIterator(); - iter.begin(); - Ogre::SceneManager* scn = iter.getNext(); - Ogre::SceneNode* node = scn->getRootSceneNode()->createChildSceneNode(); - node->pitch(Ogre::Degree(-90)); + Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); dynamicsWorld->setDebugDrawer(mDebugDrawer); isDebugCreated = true; @@ -249,6 +211,11 @@ namespace Physic return mDebugActive; } + void PhysicEngine::setSceneManager(Ogre::SceneManager* sceneMgr) + { + mSceneMgr = sceneMgr; + } + PhysicEngine::~PhysicEngine() { HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin(); @@ -338,7 +305,7 @@ namespace Physic mHeightFieldMap [name] = hf; - dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + dynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_ActorInternal|CollisionType_ActorExternal); } void PhysicEngine::removeHeightField(int x, int y) @@ -356,18 +323,21 @@ namespace Physic mHeightFieldMap.erase(name); } - void PhysicEngine::adjustRigidBody(RigidBody* body, Ogre::Vector3 position, Ogre::Quaternion rotation, - Ogre::Vector3 scaledBoxTranslation, Ogre::Quaternion boxRotation){ + void PhysicEngine::adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, + const Ogre::Vector3 &scaledBoxTranslation, const Ogre::Quaternion &boxRotation) + { btTransform tr; - rotation = rotation * boxRotation; - Ogre::Vector3 transrot = rotation * scaledBoxTranslation; + Ogre::Quaternion boxrot = rotation * boxRotation; + Ogre::Vector3 transrot = boxrot * scaledBoxTranslation; Ogre::Vector3 newPosition = transrot + position; - + tr.setOrigin(btVector3(newPosition.x, newPosition.y, newPosition.z)); - tr.setRotation(btQuaternion(rotation.x,rotation.y,rotation.z,rotation.w)); + tr.setRotation(btQuaternion(boxrot.x,boxrot.y,boxrot.z,boxrot.w)); body->setWorldTransform(tr); } - void PhysicEngine::boxAdjustExternal(std::string mesh, RigidBody* body, float scale, Ogre::Vector3 position, Ogre::Quaternion rotation){ + void PhysicEngine::boxAdjustExternal(const std::string &mesh, RigidBody* body, + float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation) + { std::string sid = (boost::format("%07.3f") % scale).str(); std::string outputstring = mesh + sid; //std::cout << "The string" << outputstring << "\n"; @@ -380,7 +350,8 @@ namespace Physic adjustRigidBody(body, position, rotation, shape->boxTranslation * scale, shape->boxRotation); } - RigidBody* PhysicEngine::createAndAdjustRigidBody(std::string mesh,std::string name,float scale, Ogre::Vector3 position, Ogre::Quaternion rotation, + RigidBody* PhysicEngine::createAndAdjustRigidBody(const std::string &mesh, const std::string &name, + float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, Ogre::Vector3* scaledBoxTranslation, Ogre::Quaternion* boxRotation) { std::string sid = (boost::format("%07.3f") % scale).str(); @@ -421,11 +392,11 @@ namespace Physic return; if(body->mCollide) { - dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + dynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_ActorInternal|CollisionType_ActorExternal); } else { - dynamicsWorld->addRigidBody(body,COL_RAYCASTING,COL_RAYCASTING|COL_WORLD); + dynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_World); } body->setActivationState(DISABLE_DEACTIVATION); if(addToMap){ @@ -441,7 +412,7 @@ namespace Physic } } - void PhysicEngine::removeRigidBody(std::string name) + void PhysicEngine::removeRigidBody(const std::string &name) { RigidBodyContainer::iterator it = ObjectMap.find(name); if (it != ObjectMap.end() ) @@ -461,7 +432,7 @@ namespace Physic } } - void PhysicEngine::deleteRigidBody(std::string name) + void PhysicEngine::deleteRigidBody(const std::string &name) { RigidBodyContainer::iterator it = ObjectMap.find(name); if (it != ObjectMap.end() ) @@ -481,7 +452,7 @@ namespace Physic } } - RigidBody* PhysicEngine::getRigidBody(std::string name) + RigidBody* PhysicEngine::getRigidBody(const std::string &name) { RigidBodyContainer::iterator it = ObjectMap.find(name); if (it != ObjectMap.end() ) @@ -497,15 +468,16 @@ namespace Physic void PhysicEngine::stepSimulation(double deltaT) { - dynamicsWorld->stepSimulation(deltaT,10, 1/60.0); + // This isn't needed as there are no dynamic objects at this point + //dynamicsWorld->stepSimulation(deltaT,10, 1/60.0); if(isDebugCreated) { mDebugDrawer->step(); } } - void PhysicEngine::addCharacter(std::string name, std::string mesh, - Ogre::Vector3 position, float scale, Ogre::Quaternion rotation) + void PhysicEngine::addCharacter(const std::string &name, const std::string &mesh, + const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation) { // Remove character with given name, so we don't make memory // leak when character would be added twice @@ -518,9 +490,8 @@ namespace Physic PhysicActorMap[name] = newActor; } - void PhysicEngine::removeCharacter(std::string name) + void PhysicEngine::removeCharacter(const std::string &name) { - //std::cout << "remove"; PhysicActorContainer::iterator it = PhysicActorMap.find(name); if (it != PhysicActorMap.end() ) { @@ -534,7 +505,7 @@ namespace Physic } } - PhysicActor* PhysicEngine::getCharacter(std::string name) + PhysicActor* PhysicEngine::getCharacter(const std::string &name) { PhysicActorContainer::iterator it = PhysicActorMap.find(name); if (it != PhysicActorMap.end() ) @@ -559,7 +530,7 @@ namespace Physic float d1 = 10000.; btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterMask = COL_WORLD|COL_RAYCASTING; + resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_Raycasting; dynamicsWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit()) { @@ -569,7 +540,7 @@ namespace Physic } btCollisionWorld::ClosestRayResultCallback resultCallback2(from, to); - resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + resultCallback2.m_collisionFilterMask = CollisionType_ActorInternal|CollisionType_ActorExternal; dynamicsWorld->rayTest(from, to, resultCallback2); float d2 = 10000.; if (resultCallback2.hasHit()) @@ -588,12 +559,12 @@ namespace Physic std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) { MyRayResultCallback resultCallback1; - resultCallback1.m_collisionFilterMask = COL_WORLD|COL_RAYCASTING; + resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_Raycasting; dynamicsWorld->rayTest(from, to, resultCallback1); std::vector< std::pair > results = resultCallback1.results; MyRayResultCallback resultCallback2; - resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + resultCallback2.m_collisionFilterMask = CollisionType_ActorInternal|CollisionType_ActorExternal; dynamicsWorld->rayTest(from, to, resultCallback2); std::vector< std::pair > actorResults = resultCallback2.results; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index f320d009d..97fbbcea4 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -18,13 +18,17 @@ class btSequentialImpulseConstraintSolver; class btCollisionDispatcher; class btDiscreteDynamicsWorld; class btHeightfieldTerrainShape; -struct playerMove; namespace BtOgre { class DebugDrawer; } +namespace Ogre +{ + class SceneManager; +} + namespace MWWorld { class World; @@ -39,6 +43,14 @@ namespace Physic class PhysicEngine; class RigidBody; + enum CollisionType { + CollisionType_Nothing = 0, //= 1600 + void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } + void operator delete (void * Data) { _aligned_free (Data); } +#endif private: - + OEngine::Physic::RigidBody* mBody; Ogre::Vector3 mBoxScaledTranslation; btQuaternion mBoxRotationInverse; Ogre::Quaternion mBoxRotation; + float verticalForce; + bool onGround; bool collisionMode; std::string mMesh; PhysicEngine* mEngine; std::string mName; - playerMove* pmove; - }; /** @@ -185,19 +198,21 @@ namespace Physic * Creates a RigidBody. It does not add it to the simulation. * After created, the body is set to the correct rotation, position, and scale */ - RigidBody* createAndAdjustRigidBody(std::string mesh,std::string name,float scale, Ogre::Vector3 position, Ogre::Quaternion rotation, + RigidBody* createAndAdjustRigidBody(const std::string &mesh, const std::string &name, + float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, Ogre::Vector3* scaledBoxTranslation = 0, Ogre::Quaternion* boxRotation = 0); /** * Adjusts a rigid body to the right position and rotation */ - void adjustRigidBody(RigidBody* body, Ogre::Vector3 position, Ogre::Quaternion rotation, - Ogre::Vector3 scaledBoxTranslation = Ogre::Vector3::ZERO, Ogre::Quaternion boxRotation = Ogre::Quaternion::IDENTITY); + void adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, + const Ogre::Vector3 &scaledBoxTranslation = Ogre::Vector3::ZERO, + const Ogre::Quaternion &boxRotation = Ogre::Quaternion::IDENTITY); /** Mainly used to (but not limited to) adjust rigid bodies based on box shapes to the right position and rotation. */ - void boxAdjustExternal(std::string mesh, RigidBody* body, float scale, Ogre::Vector3 position, Ogre::Quaternion rotation); + void boxAdjustExternal(const std::string &mesh, RigidBody* body, float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); /** * Add a HeightField to the simulation */ @@ -218,35 +233,35 @@ namespace Physic /** * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. */ - void removeRigidBody(std::string name); + void removeRigidBody(const std::string &name); /** * Delete a RigidBody, and remove it from RigidBodyMap. */ - void deleteRigidBody(std::string name); + void deleteRigidBody(const std::string &name); /** * Return a pointer to a given rigid body. * TODO:check if exist */ - RigidBody* getRigidBody(std::string name); + RigidBody* getRigidBody(const std::string &name); /** * Create and add a character to the scene, and add it to the ActorMap. */ - void addCharacter(std::string name, std::string mesh, - Ogre::Vector3 position, float scale, Ogre::Quaternion rotation); + void addCharacter(const std::string &name, const std::string &mesh, + const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation); /** * Remove a character from the scene. TODO:delete it! for now, a small memory leak^^ done? */ - void removeCharacter(std::string name); + void removeCharacter(const std::string &name); /** * Return a pointer to a character * TODO:check if the actor exist... */ - PhysicActor* getCharacter(std::string name); + PhysicActor* getCharacter(const std::string &name); /** * This step the simulation of a given time. @@ -274,6 +289,8 @@ namespace Physic void getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max); + void setSceneManager(Ogre::SceneManager* sceneMgr); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ @@ -310,11 +327,12 @@ namespace Physic typedef std::map PhysicActorContainer; PhysicActorContainer PhysicActorMap; + Ogre::SceneManager* mSceneMgr; + //debug rendering BtOgre::DebugDrawer* mDebugDrawer; bool isDebugCreated; bool mDebugActive; - }; diff --git a/libs/openengine/bullet/pmove.cpp b/libs/openengine/bullet/pmove.cpp deleted file mode 100644 index 8d98a482e..000000000 --- a/libs/openengine/bullet/pmove.cpp +++ /dev/null @@ -1,2115 +0,0 @@ -/* -This source file is a *modified* version of bg_pmove.c from the Quake 3 Arena source code, -which was released under the GNU GPL (v2) in 2005. -Quake 3 Arena is copyright (C) 1999-2005 Id Software, Inc. -*/ - - -#include "pmove.h" - - - -//#include "bprintf.h" - -//#include "..\..\ESMParser\ESMParser\CELL.h" - -//#include "GameTime.h" - -//#include "Sound.h" - -//#include "..\..\ESMParser\ESMParser\SNDG.h" -//#include "..\..\ESMParser\ESMParser\SOUN.h" - -#include - -//SceneInstance* global_lastscene = NULL; - -// Forward declaration: -void PM_AirMove(); - -static playerMove* pm = NULL; - -//extern std::map ExtCellLookup; - -static struct playermoveLocal -{ - playermoveLocal() : frametime(1.0f / 20.0f), groundPlane(true), walking(true), msec(50) - { - forward = Ogre::Vector3(0.0f, 0.0f, 0.0f); - right = Ogre::Vector3(0.0f, 0.0f, 0.0f); - up = Ogre::Vector3(0.0f, 0.0f, 0.0f); - - previous_origin = Ogre::Vector3(0.0f, 0.0f, 0.0f); - previous_velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); - } - - traceResults groundTrace; - - //SceneInstance* scene; - - float frametime; // in seconds (usually something like 0.01f) - float impactSpeed; - - Ogre::Vector3 forward; - Ogre::Vector3 right; - Ogre::Vector3 up; - - int msec; - - Ogre::Vector3 previous_origin, previous_velocity; - - int previous_waterlevel; // the waterlevel before this pmove - - bool groundPlane; // if we're standing on a groundplane this frame - - bool walking; - int waterHeight; - bool hasWater; - bool isInterior; - -} pml; - -static inline void PM_ClipVelocity(const Ogre::Vector3& in, const Ogre::Vector3& normal, Ogre::Vector3& out, const float overbounce) -{ - float backoff; - //float change; - //int i; - - // backoff = in dot normal - //backoff = DotProduct (in, normal); - backoff = in.dotProduct(normal); - - if ( backoff < 0 ) - backoff *= overbounce; - else - backoff /= overbounce; - - // change = normal * backoff - // out = in - change - /*for ( i=0 ; i<3 ; i++ ) - { - change = normal[i]*backoff; - out[i] = in[i] - change; - - }*/ - float changex = normal.x * backoff; - out.x = in.x - changex; - float changey = normal.y * backoff; - out.y = in.y - changey; - float changez = normal.z * backoff; - out.z = in.z - changez; -} - -float VectorNormalize2( const Ogre::Vector3& v, Ogre::Vector3& out) -{ - float length, ilength; - - length = v.x * v.x+ v.y * v.y + v.z * v.z; - length = sqrt(length); - - if (length) - { -#ifndef Q3_VM // bk0101022 - FPE related -// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); -#endif - ilength = 1 / length; - out.x= v.x * ilength; - out.y = v.y * ilength; - out.z = v.z * ilength; - } else - { -#ifndef Q3_VM // bk0101022 - FPE related -// assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) ); -#endif - //VectorClear( out ); - out.x = 0; out.y = 0; out.z = 0; - } - - return length; - -} - - -float VectorNormalize(Ogre::Vector3& out) -{ - float length, ilength; - - length = out.x * out.x + out.y * out.y + out.z * out.z; - length = sqrt(length); - - if (length) - { -#ifndef Q3_VM // bk0101022 - FPE related -// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); -#endif - ilength = 1 / length; - out.x = out.x * ilength; - out.y = out.y * ilength; - out.z = out.z * ilength; - } - - return length; - -} - -/* -================== -PM_SlideMove - -Returns qtrue if the velocity was clipped in some way -================== -*/ - -bool PM_SlideMove( bool gravity ) -{ - int bumpcount, numbumps; - Ogre::Vector3 dir; - float d; - int numplanes; - Ogre::Vector3 planes[MAX_CLIP_PLANES]; - Ogre::Vector3 primal_velocity; - Ogre::Vector3 clipVelocity; - int i, j, k; - struct traceResults trace; - Ogre::Vector3 end(0,0,0); - float time_left; - float into; - Ogre::Vector3 endVelocity(0,0,0); - Ogre::Vector3 endClipVelocity(0,0,0); - - numbumps = 4; - - // primal_velocity = pm->ps->velocity - //VectorCopy (pm->ps->velocity, primal_velocity); - primal_velocity = pm->ps.velocity; - - if ( gravity ) - { - // endVelocity = pm->ps->velocity - vec3(0, 0, pm->ps->gravity * pml.frametime) - //VectorCopy( pm->ps->velocity, endVelocity ); - endVelocity = pm->ps.velocity; - //endVelocity[2] -= pm->ps->gravity * pml.frametime; - endVelocity.z -= pm->ps.gravity * pml.frametime; - - // pm->ps->velocity = avg(pm->ps->velocity.z, endVelocity.z) - //pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; - pm->ps.velocity.z= (pm->ps.velocity.z + endVelocity.z) * 0.5f; - - //primal_velocity[2] = endVelocity[2]; - primal_velocity.z = endVelocity.z; - - if ( pml.groundPlane ) - // slide along the ground plane - //PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); - PM_ClipVelocity(pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP); - } - - time_left = pml.frametime; - - // never turn against the ground plane - if ( pml.groundPlane ) - { - numplanes = 1; - - // planes[0] = pml.groundTrace.plane.normal - //VectorCopy( pml.groundTrace.plane.normal, planes[0] ); - planes[0] = pml.groundTrace.planenormal; - } else - numplanes = 0; - - // never turn against original velocity - VectorNormalize2( pm->ps.velocity, planes[numplanes] ); - numplanes++; - - for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) - { - - // calculate position we are trying to move to - //VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); - end = pm->ps.origin + pm->ps.velocity * time_left; - - // see if we can make it there - //pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemaskg); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&(end), *(const D3DXVECTOR3* const)&(pm->ps.velocity), 0, pml.traceObj); - newtrace(&trace, pm->ps.origin, end, pm->ps.halfExtents, Ogre::Math::DegreesToRadians (pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - - if (trace.allsolid) - { - // entity is completely trapped in another solid - //pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration - pm->ps.velocity = Ogre::Vector3(0,0,0); - return true; - } - - if (trace.fraction > 0) - // actually covered some distance - //VectorCopy (trace.endpos, pm->ps->origin); - pm->ps.origin = trace.endpos; - - if (trace.fraction == 1) - break; // moved the entire distance - - // save entity for contact8 - //PM_AddTouchEnt( trace.entityNum ); - - time_left -= time_left * trace.fraction; - - if (numplanes >= MAX_CLIP_PLANES) - { - // this shouldn't really happen - //VectorClear( pm->ps->velocity ); - pm->ps.velocity = Ogre::Vector3(0,0,0); - return true; - } - - // - // if this is the same plane we hit before, nudge velocity - // out along it, which fixes some epsilon issues with - // non-axial planes - // - for ( i = 0 ; i < numplanes ; i++ ) - { - if (trace.planenormal.dotProduct(planes[i]) > 0.99) //OGRE::VECTOR3 ? - //if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) - { - // pm->ps->velocity += (trace.plane.normal + pm->ps->velocity) - //VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); - pm->ps.velocity = trace.planenormal + pm->ps.velocity; - break; - } - } - - if ( i < numplanes ) - continue; - - //VectorCopy (trace.plane.normal, planes[numplanes]); - planes[numplanes] = trace.planenormal; - numplanes++; - - // - // modify velocity so it parallels all of the clip planes - // - - // find a plane that it enters - for ( i = 0 ; i < numplanes ; i++ ) - { - //into = DotProduct( pm->ps->velocity, planes[i] ); - into = pm->ps.velocity.dotProduct(planes[i]); - if ( into >= 0.1 ) - continue; // move doesn't interact with the plane - - - if(planes[i].x >= .70) - { - pm->ps.velocity.z = 0; - return true; - } - // see how hard we are hitting things - if ( -into > pml.impactSpeed ) - pml.impactSpeed = -into; - - // slide along the plane - //PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); - PM_ClipVelocity(pm->ps.velocity, planes[i], clipVelocity, OVERCLIP); - - // slide along the plane - PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); - - // see if there is a second plane that the new move enters - for ( j = 0 ; j < numplanes ; j++ ) - { - if ( j == i ) - continue; - - if (clipVelocity.dotProduct(planes[j]) >= 0.1) - //if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) - continue; // move doesn't interact with the plane - - - - - //pm->ps.velocity = Ogre::Vector3(0,0,0); - //return true; - - - // try clipping the move to the plane - PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); - PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); - - // see if it goes back into the first clip plane - if (clipVelocity.dotProduct(planes[i]) >= 0) - //if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) - continue; - - - // slide the original velocity along the crease - //dProduct (planes[i], planes[j], dir); - dir = planes[i].crossProduct(planes[j]) ; - - //VectorNormalize( dir ); - //D3DXVec3Normalize( (D3DXVECTOR3* const)&dir, (const D3DXVECTOR3* const)&dir); - VectorNormalize(dir); - - //d = DotProduct( dir, pm->ps->velocity ); - d = dir.dotProduct(pm->ps.velocity); - - //VectorScale( dir, d, clipVelocity ); - clipVelocity = dir * d; - - //CrossProduct (planes[i], planes[j], dir); - dir = planes[i].crossProduct(planes[j]) ; - - - //VectorNormalize( dir ); - //D3DXVec3Normalize( (D3DXVECTOR3* const)&dir, (const D3DXVECTOR3* const)&dir); - VectorNormalize(dir); - - //d = DotProduct( dir, endVelocity ); - d = dir.dotProduct(endVelocity); - - //VectorScale( dir, d, endClipVelocity ); - endClipVelocity = dir * d; - - // see if there is a third plane the the new move enters - for ( k = 0 ; k < numplanes ; k++ ) - { - - if ( k == i || k == j ) - continue; - - if (clipVelocity.dotProduct(planes[k]) >= 0.1) - //if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) - continue; // move doesn't interact with the plane - - // stop dead at a tripple plane interaction - //VectorClear( pm->ps->velocity ); - //printf("Stop dead at a triple plane interaction\n"); - pm->ps.velocity = Ogre::Vector3(0,0,0); - return true; - } - } - - // if we have fixed all interactions, try another move - //VectorCopy( clipVelocity, pm->ps->velocity ); - pm->ps.velocity = clipVelocity; - - //VectorCopy( endClipVelocity, endVelocity ); - endVelocity = endClipVelocity; - break; - } - } - - if ( gravity ) - //VectorCopy( endVelocity, pm->ps->velocity ); - pm->ps.velocity = endVelocity; - - // don't change velocity if in a timer (FIXME: is this correct?) - if ( pm->ps.pm_time ) - //VectorCopy( primal_velocity, pm->ps->velocity ); - pm->ps.velocity = primal_velocity; - - //return ( (qboolean)(bumpcount != 0) ); - return bumpcount != 0; -} - -/* -================== -PM_StepSlideMove - -================== -*/ -int PM_StepSlideMove( bool gravity ) -{ - Ogre::Vector3 start_o, start_v; - Ogre::Vector3 down_o, down_v; - traceResults trace; -// float down_dist, up_dist; -// vec3_t delta, delta2; - Ogre::Vector3 up, down; - float stepSize; - - //std::cout << "StepSlideMove\n"; - // start_o = pm->ps->origin - //VectorCopy (pm->ps->origin, start_o); - start_o = pm->ps.origin; - - // start_v = pm->ps->velocity - //VectorCopy (pm->ps->velocity, start_v); - start_v = pm->ps.velocity; - - if ( PM_SlideMove( gravity ) == false ) - return 1; // we got exactly where we wanted to go first try - - - // down = start_o - vec3(0, 0, STEPSIZE) - //VectorCopy(start_o, down); - down = start_o; - down.z -= STEPSIZE; - - //pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - //tracefunc(&trace, start_o, down, , 0, pml.scene); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&start_o, *(const D3DXVECTOR3* const)&down, D3DXVECTOR3(0.0f, -STEPSIZE, 0.0f), 0, pml.traceObj); - newtrace(&trace, down, start_o, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - - // up = vec3(0, 0, 1) - //VectorSet(up, 0, 0, 1); - up = Ogre::Vector3(0.0f, 0.0f, 1.0f); - - // never step up when you still have up velocity - //if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || DotProduct(trace.plane.normal, up) < 0.7)) - if (pm->ps.velocity.z > 0 && ( - trace.fraction == 1.0 || trace.planenormal.dotProduct(up) < 0.7 - ) ) - return 2; - - // down_o = pm->ps->origin - //VectorCopy (pm->ps->origin, down_o); - down_o = pm->ps.origin; - - // down_v = pm->ps->velocity - //VectorCopy (pm->ps->velocity, down_v); - down_v = pm->ps.velocity; - - // up = start_o + vec3(0, 0, STEPSIZE) - //VectorCopy (start_o, up); - up = start_o; - //up[2] += STEPSIZE; - up.z += STEPSIZE; - - // test the player position if they were a stepheight higher - //pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&start_o, *(const D3DXVECTOR3* const)&up, D3DXVECTOR3(0.0f, STEPSIZE, 0.0f), 0, pml.traceObj); - newtrace(&trace, start_o, up, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - if ( trace.allsolid ) - { - //if ( pm->debugLevel ) - //Com_Printf("%i:bend can't step\n", c_pmove); - //bprintf("bend can't step\n"); - return 3; // can't step up - } - - //stepSize = trace.endpos[2] - start_o[2]; - stepSize = trace.endpos.z - start_o.z; - - // try slidemove from this position - //VectorCopy (trace.endpos, pm->ps->origin); // pm->ps->origin = trace.endpos - pm->ps.origin = trace.endpos; - //VectorCopy (start_v, pm->ps->velocity); // pm->ps->velocity = start_v - pm->ps.velocity = start_v; - - PM_SlideMove( gravity ); - - // push down the final amount - - // down = pm->ps->origin - vec3(0, 0, stepSize) - //VectorCopy (pm->ps->origin, down); - down = pm->ps.origin; - //down[2] -= stepSize; - down.z -= stepSize; - - - //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&down, D3DXVECTOR3(0.0f, -STEPSIZE, 0.0f), 0, pml.traceObj); - newtrace(&trace, pm->ps.origin, down, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - if ( !trace.allsolid ) - //VectorCopy (trace.endpos, pm->ps->origin); - pm->ps.origin = trace.endpos; - - if ( trace.fraction < 1.0 ) - //PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); - PM_ClipVelocity(pm->ps.velocity, trace.planenormal, pm->ps.velocity, OVERCLIP); - - { - // use the step move - float delta; - - //delta = pm->ps->origin[2] - start_o[2]; - delta = pm->ps.origin.z - start_o.z; - if ( delta > 2 ) - { - pm->ps.counter = 10; - - /* - if (gravity) - printf("g on: %f ", delta); - else - printf("g off: %f ", delta); - - if ( delta < 7 ) - printf("stepped 3 < x < 7\n"); - //PM_AddEvent( EV_STEP_4 ); - else if ( delta < 11 ) - printf("stepped 7 < x < 11\n"); - //PM_AddEvent( EV_STEP_8 ); - else if ( delta < 15 ) - printf("stepped 11 < x < 15\n"); - //PM_AddEvent( EV_STEP_12 ); - else - printf("stepped 15+\n"); - //PM_AddEvent( EV_STEP_16 ); - */ - } - /*if ( pm->debugLevel ) - Com_Printf("%i:stepped\n", c_pmove);*/ - } - - return 4; -} - -void PM_Friction(void) -{ - - Ogre::Vector3 vec; - float* vel; - float speed, newspeed, control; - float drop; - - vel = &(pm->ps.velocity.x); - - // vec = vel - //VectorCopy( vel, vec ); - vec = pm->ps.velocity; - - if ( pml.walking ) - //vec[2] = 0; // ignore slope movement - vec.z = 0; - - //speed = VectorLength(vec); - speed = vec.length(); - if (speed < 1) - { - vel[0] = 0; - vel[1] = 0; // allow sinking underwater - // FIXME: still have z friction underwater? - //bprintf("Static friction (vec = [%f, %f, %f]) (vec.length = %f)\n", vec.x, vec.y, vec.z, speed); - return; - } - - drop = 0; - - // apply ground friction - if ( pm->ps.waterlevel <= 1 ) - { - if ( pml.walking )//&& !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) - { - // if getting knocked back, no friction - //if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) - { - control = (speed < pm_stopspeed) ? pm_stopspeed : speed; - drop += control * pm_friction * pml.frametime; - } - } - } - - // apply water friction even if just wading - if ( pm->ps.waterlevel ) - drop += speed * pm_waterfriction * pm->ps.waterlevel * pml.frametime; - - // apply flying friction - /*if ( pm->ps->powerups[PW_FLIGHT]) - drop += speed * pm_flightfriction * pml.frametime; - - if ( pm->ps->pm_type == PM_SPECTATOR) - drop += speed * pm_spectatorfriction * pml.frametime;*/ - if (pm->ps.move_type == PM_SPECTATOR) - drop += speed * pm_flightfriction * pml.frametime; - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) - newspeed = 0; - - newspeed /= speed; - - // vel *= newspeed - vel[0] = vel[0] * newspeed; - vel[1] = vel[1] * newspeed; - vel[2] = vel[2] * newspeed; -} - -float PM_CmdScale(playerMove::playercmd* const cmd) -{ - int max; - float total; - float scale; - - max = abs( cmd->forwardmove ); - if ( abs( cmd->rightmove ) > max ) - max = abs( cmd->rightmove ); - - if ( abs( cmd->upmove ) > max ) - max = abs( cmd->upmove ); - - if ( !max ) - return 0; - - total = sqrtf( (const float)(cmd->forwardmove * cmd->forwardmove - + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove) ); - scale = (float)pm->ps.speed * max / ( 127.0f * total ); - if(pm->ps.move_type == PM_NOCLIP) - scale *= 10; - - return scale; -} - -static void PM_Accelerate( Ogre::Vector3& wishdir, float wishspeed, float accel ) -{ -// int i; - float addspeed, accelspeed, currentspeed; - - - // currentspeed = pm->ps->velocity dot wishdir - //currentspeed = DotProduct (pm->ps->velocity, wishdir); - currentspeed = pm->ps.velocity.dotProduct(wishdir); - - addspeed = wishspeed - currentspeed; - if (addspeed <= 0) - return; - - accelspeed = accel * pml.frametime * wishspeed; - - // Clamp accelspeed at addspeed - if (accelspeed > addspeed) - accelspeed = addspeed; - - // pm->ps->velocity += accelspeed * wishdir - //for (i=0 ; i<3 ; i++) - //pm->ps->velocity[i] += accelspeed * wishdir[i]; - pm->ps.velocity += (wishdir * accelspeed); - //pm->ps.velocity = wishdir * wishspeed; //New, for instant acceleration - -} - -static bool PM_CheckJump(void) -{ - //if ( pm->ps->pm_flags & PMF_RESPAWNED ) - //return qfalse; // don't allow jump until all buttons are up - - if ( pm->cmd.upmove < 10 ) - // not holding jump - return false; - - pm->cmd.upmove = 0; - - // must wait for jump to be released - /*if ( pm->ps->pm_flags & PMF_JUMP_HELD ) - { - // clear upmove so cmdscale doesn't lower running speed - pm->cmd.upmove = 0; - return false; - }*/ - - pml.groundPlane = false; // jumping away - pml.walking = false; - //pm->ps->pm_flags |= PMF_JUMP_HELD; - - pm->ps.groundEntityNum = ENTITYNUM_NONE; - pm->ps.velocity.z = pm->ps.jump_velocity; - pm->ps.bSnap = false; - //PM_AddEvent( EV_JUMP ); - - /*if ( pm->cmd.forwardmove >= 0 ) - { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } - else - { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - }*/ - - return true; -} - -static void PM_WaterMove( playerMove* const pm ) -{ - //int i; - //vec3_t wishvel; - Ogre::Vector3 wishvel; - float wishspeed; - //vec3_t wishdir; - Ogre::Vector3 wishdir; - float scale; - float vel; - - pm->ps.bSnap = false; - - /*if ( PM_CheckWaterJump() ) - { - PM_WaterJumpMove(); - return; - }*/ -#if 0 - // jump = head for surface - if ( pm->cmd.upmove >= 10 ) { - if (pm->ps->velocity[2] > -300) { - if ( pm->watertype == CONTENTS_WATER ) { - pm->ps->velocity[2] = 100; - } else if (pm->watertype == CONTENTS_SLIME) { - pm->ps->velocity[2] = 80; - } else { - pm->ps->velocity[2] = 50; - } - } - } -#endif - PM_Friction (); - - if (pm->cmd.forwardmove || pm->cmd.rightmove) - { - //NEEDS TO BE REWRITTEN FOR OGRE TIME--------------------------------------------------- - /* - static const TimeTicks footstep_duration = GetTimeFreq(); // make each splash last 1.0s - static TimeTicks lastStepTime = 0; - const TimeTicks thisStepTime = GetTimeQPC(); - static bool lastWasLeft = false; - if (thisStepTime > lastStepTime) - { - if (pm->cmd.ducking) - lastStepTime = thisStepTime + footstep_duration * 2; // splashes while ducking are twice as slow - else - lastStepTime = thisStepTime + footstep_duration; - - lastWasLeft = !lastWasLeft; - */ - //-----------------jhooks1 - - /* - namestruct defaultCreature; - const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, lastWasLeft ? SNDG::r_swim : SNDG::l_swim); - if (sndg) - { - const namestruct& SOUNID = sndg->soundID; - const SOUN* const soun = SOUN::GetSound(SOUNID); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); - } - }*/ - //Sound, ignore for now -- jhooks1 - //} - } - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) - { - /*wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = -60; // sink towards bottom - */ - wishvel.x = 0; - wishvel.z = -60; - wishvel.y = 0; - } - else - { - /*for (i=0 ; i<3 ; i++) - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;*/ - wishvel = pml.forward * scale * pm->cmd.forwardmove + pml.right * scale * pm->cmd.rightmove; - - //wishvel[2] += scale * pm->cmd.upmove; - wishvel.z += pm->cmd.upmove * scale; - } - - //VectorCopy (wishvel, wishdir); - wishdir = wishvel; - wishspeed = VectorNormalize(wishdir); - - if ( wishspeed > pm->ps.speed * pm_swimScale ) - wishspeed = pm->ps.speed * pm_swimScale; - - PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); - - // make sure we can go up slopes easily under water - //if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) - if (pml.groundPlane && pm->ps.velocity.dotProduct(pml.groundTrace.planenormal) < 0.0f) - { - //vel = VectorLength(pm->ps->velocity); - vel = pm->ps.velocity.length(); - - // slide along the ground plane - //PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); - PM_ClipVelocity(pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP); - - VectorNormalize(pm->ps.velocity); - //VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - pm->ps.velocity = pm->ps.velocity * vel; - } - - PM_SlideMove( false ); -} - -/* -=================== -PM_WalkMove - -=================== -*/ -static void PM_WalkMove( playerMove* const pmove ) -{ -// int i; - Ogre::Vector3 wishvel; - float fmove, smove; - Ogre::Vector3 wishdir; - float wishspeed; - float scale; - playerMove::playercmd cmd; - float accelerate; - float vel; - //pm->ps.gravity = 4000; - - //std::cout << "Player is walking\n"; - - if ( pm->ps.waterlevel > 2 && //DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) - pml.forward.dotProduct(pml.groundTrace.planenormal) > 0.0f) - { - // begin swimming - PM_WaterMove(pmove); - return; - } - - - if ( PM_CheckJump () ) - { - - // jumped away - if ( pm->ps.waterlevel > 1 ) - PM_WaterMove(pmove); - else - PM_AirMove(); - //printf("Jumped away\n"); - return; - } - - // Footsteps time - if (pmove->cmd.forwardmove || pmove->cmd.rightmove) - { - bool step_underwater = false; - //if (pmove->traceObj) - //{ - - - //jhooks1 - Water handling, deal with later - - - - if (pmove->hasWater) - { - if (pmove->hasWater ) - { - const float waterHeight = pmove->waterHeight; - const float waterSoundStepHeight = waterHeight + pm->ps.halfExtents.y; - if (pmove->ps.origin.y < waterSoundStepHeight) - step_underwater = true; - } - } - //} - - /* - static const TimeTicks footstep_duration = GetTimeFreq() / 2; // make each footstep last 500ms - static TimeTicks lastStepTime = 0; - const TimeTicks thisStepTime = GetTimeQPC(); - static bool lastWasLeft = false; - if (thisStepTime > lastStepTime) - { - if (pmove->cmd.ducking) - lastStepTime = thisStepTime + footstep_duration * 2; // footsteps while ducking are twice as slow - else - lastStepTime = thisStepTime + footstep_duration; - - lastWasLeft = !lastWasLeft; - */ - - if (step_underwater) - { - /* - const namestruct ns(lastWasLeft ? "FootWaterRight" : "FootWaterLeft"); - const SOUN* const soun = SOUN::GetSound(ns); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); - }*/ - } - else - { - /* - namestruct defaultCreature; - const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, lastWasLeft ? SNDG::r_foot : SNDG::l_foot); - if (sndg) - { - const namestruct& SOUNID = sndg->soundID; - const SOUN* const soun = SOUN::GetSound(SOUNID); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); - } - }*/ - } - } - - - PM_Friction (); - - - //bprintf("vel: (%f, %f, %f)\n", pm->ps.velocity.x, pm->ps.velocity.y, pm->ps.velocity.z); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - //PM_SetMovementDir(); - - // project moves down to flat plane - //pml.forward[2] = 0; - pml.forward.z = 0; - - //pml.right[2] = 0; - pml.right.z = 0; - //std::cout << "Further down" << pm->ps.velocity << "\n"; - - - // project the forward and right directions onto the ground plane - PM_ClipVelocity (pml.forward, pml.groundTrace.planenormal, pml.forward, OVERCLIP ); - PM_ClipVelocity (pml.right, pml.groundTrace.planenormal, pml.right, OVERCLIP ); - //std::cout << "Clip velocity" << pm->ps.velocity << "\n"; - // - - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - //pml.forward = pml.forward.normalise(); - //pml.right = pml.right.normalise(); - //std::cout << "forward2" << pml.forward << "\n"; - //std::cout << "right2" << pml.right << "\n"; - - - // wishvel = (pml.forward * fmove) + (pml.right * smove); - //for ( i = 0 ; i < 3 ; i++ ) - //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; - wishvel = pml.forward * fmove + pml.right * smove; - - - //bprintf("f: (%f, %f, %f), s: (%f, %f, %f)\n", fmove, smove); - - - // when going up or down slopes the wish velocity should Not be zero -// wishvel[2] = 0; - - // wishdir = wishvel - //VectorCopy (wishvel, wishdir); - //wishvel = wishdir; - wishdir = wishvel; - - wishspeed = VectorNormalize(wishdir); - //std::cout << "Wishspeed: " << wishspeed << "\n"; - wishspeed *= scale; - //std::cout << "Wishspeed scaled:" << wishspeed << "\n"; - - // clamp the speed lower if ducking - if ( pm->cmd.ducking ) - if ( wishspeed > pm->ps.speed * pm_duckScale ) - wishspeed = pm->ps.speed * pm_duckScale; - - // clamp the speed lower if wading or walking on the bottom - if ( pm->ps.waterlevel ) - { - float waterScale; - - waterScale = pm->ps.waterlevel / 3.0f; - waterScale = 1.0f - ( 1.0f - pm_swimScale ) * waterScale; - if ( wishspeed > pm->ps.speed * waterScale ) - wishspeed = pm->ps.speed * waterScale; - } - - // when a player gets hit, they temporarily lose - // full control, which allows them to be moved a bit - //if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) - //accelerate = pm_airaccelerate; - //else - accelerate = pm_accelerate; - - - PM_Accelerate (wishdir, wishspeed, accelerate); - //std::cout << "Velocityafter: " << pm->ps.velocity << "\n"; - - //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); - //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); - - //if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) - //pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - //else - //{ - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - //} - - //vel = VectorLength(pm->ps->velocity); - vel = pm->ps.velocity.length(); - //std::cout << "The length" << vel << "\n"; - - // slide along the ground plane - PM_ClipVelocity (pm->ps.velocity, pml.groundTrace.planenormal, - pm->ps.velocity, OVERCLIP ); - //std::cout << "Velocity clipped" << pm->ps.velocity << "\n"; - - // don't decrease velocity when going up or down a slope - VectorNormalize(pm->ps.velocity); - //pm->ps.velocity = pm->ps.velocity.normalise(); - - //std::cout << "Final:" << pm->ps.velocity << "\n"; - //VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - pm->ps.velocity = pm->ps.velocity * vel; - - // don't do anything if standing still - //if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) - if (!pm->ps.velocity.x && !pm->ps.velocity.z) - return; - - PM_StepSlideMove( false ); - - //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); - - -} - -void PM_UpdateViewAngles( playerMove::playerStruct* const ps, playerMove::playercmd* const cmd ) -{ - short temp; - int i; - - //while(1); - - //if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) - //return; // no view changes at all - - //if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) - //return; // no view changes at all - - // circularly clamp the angles with deltas - //bprintf("View angles: %i, %i, %i\n", cmd->angles[0], cmd->angles[1], cmd->angles[2]); - for (i = 0 ; i < 3 ; i++) - { - temp = cmd->angles[i];// + ps->delta_angles[i]; - //if ( i == PITCH ) - { - // don't let the player look up or down more than 90 degrees - /*if ( temp > 16000 ) - { - ps->delta_angles[i] = 16000 - cmd->angles[i]; - temp = 16000; - } - else if ( temp < -16000 ) - { - ps->delta_angles[i] = -16000 - cmd->angles[i]; - temp = -16000; - }*/ - } - (&(ps->viewangles.x) )[i] = SHORT2ANGLE(temp); - //cmd->angles[i] += ps->delta_angles[i]; - } - //ps->delta_angles[0] = ps->delta_angles[1] = ps->delta_angles[2] = 0; - -} - -void AngleVectors( const Ogre::Vector3& angles, Ogre::Vector3* const forward, Ogre::Vector3* const right, Ogre::Vector3* const up) -{ - float angle; - static float sr, sp, sy, cr, cp, cy; - // static to help MS compiler fp bugs - - //angle = angles[YAW] * (M_PI*2 / 360); - angle = angles.x * (M_PI * 2.0f / 360.0f); - sp = sinf(angle); - cp = cosf(angle); - - //angle = angles[PITCH] * (M_PI*2 / 360); - angle = angles.y * (-M_PI * 2.0f / 360.0f); - sy = sinf(angle); - cy = cosf(angle); - - //angle = angles[ROLL] * (M_PI*2 / 360); - angle = angles.z * (M_PI * 2.0f / 360.0f); - sr = sinf(angle); - cr = cosf(angle); - - if (forward) - { - forward->x = cp * cy; - forward->y = cp * sy; - forward->z = -sp; - } - if (right) - { - right->x = (-1 * sr * sp * cy + -1 * cr * -sy); - right->y = (-1 * sr * sp * sy + -1 * cr * cy); - right->z = 0; - } - if (up) - { - up->x =(cr * sp * cy + -sr * -sy); - up->y=(cr * sp * sy + -sr * cy); - up->z = cr * cp; - } - -} - -void PM_GroundTraceMissed() -{ - traceResults trace; - Ogre::Vector3 point; - //We should not have significant upwards velocity when in the air, unless we jumped. - //This code protects against flying into the air when moving at high speeds. - //Z velocity is set to 50, instead of 0, to help move up certain steps. - - //std::cout << "Ground trace missed\n"; - // we just transitioned into freefall - //if ( pm->debugLevel ) - //Com_Printf("%i:lift\n", c_pmove); - - // if they aren't in a jumping animation and the ground is a ways away, force into it - // if we didn't do the trace, the player would be backflipping down staircases - //VectorCopy( pm->ps->origin, point ); - point = pm->ps.origin; - //point[2] -= 64; - point.z -= 32; - - //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -64.0f, 0.0f), 0, pml.traceObj); - newtrace(&trace, pm->ps.origin, point, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - //It hit the ground below - if ( trace.fraction < 1.0 && pm->ps.origin.z > trace.endpos.z) - { - pm->ps.origin = trace.endpos; - pml.walking = true; - pml.groundPlane = true; - pm->ps.groundEntityNum = trace.entityNum; - - } - else{ - pm->ps.groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = false; - pml.walking = false; - pm->ps.bSnap = false; - } - - -} - -static bool PM_CorrectAllSolid(traceResults* const trace) -{ - int i, j, k; - Ogre::Vector3 point; - - //if ( pm->debugLevel ) - //Com_Printf("%i:allsolid\n", c_pmove); - //bprintf("allsolid\n"); - - // jitter around - for (i = -1; i <= 1; i++) - { - for (j = -1; j <= 1; j++) - { - for (k = -1; k <= 1; k++) - { - //VectorCopy(pm->ps->origin, point); - point = pm->ps.origin; - - /*point[0] += (float) i; - point[1] += (float) j; - point[2] += (float) k;*/ - point += Ogre::Vector3( (const float)i, (const float)j, (const float)k); - - //pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - //tracefunc(trace, *(const D3DXVECTOR3* const)&point, *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, 0.0f, 0.0f), 0, pml.traceObj); - newtrace(trace, point, point, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - - if ( !trace->allsolid ) - { - /*point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25;*/ - point = pm->ps.origin; - point.z -= 0.25f; - - //pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - //tracefunc(trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -0.25f, 0.0f), 0, pml.traceObj); - newtrace(trace, pm->ps.origin, point, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - pml.groundTrace = *trace; - return true; - } - } - } - } - - //pm->ps->groundEntityNum = ENTITYNUM_NONE; - pm->ps.groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = false; - pml.walking = false; - - return false; -} - -static void PM_CrashLand( void ) -{ - float delta; - float dist ; - float vel, acc; - float t; - float a, b, c, den; - - // decide which landing animation to use - /*if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) - PM_ForceLegsAnim( LEGS_LANDB ); - else - PM_ForceLegsAnim( LEGS_LAND ); - - pm->ps->legsTimer = TIMER_LAND;*/ - - // calculate the exact velocity on landing - //dist = pm->ps->origin[2] - pml.previous_origin[2]; - - dist = pm->ps.origin.z - pml.previous_origin.z; - - //vel = pml.previous_velocity[2]; - vel = pml.previous_velocity.z; - - //acc = -pm->ps->gravity; - acc = -pm->ps.gravity; - - a = acc / 2; - b = vel; - c = -dist; - - den = b * b - 4 * a * c; - if ( den < 0 ) - return; - - t = (-b - sqrtf( den ) ) / ( 2 * a ); - - delta = vel + t * acc; - delta = delta * delta * 0.0001f; - - // ducking while falling doubles damage - /*if ( pm->ps->pm_flags & PMF_DUCKED ) - delta *= 2;*/ - if (pm->cmd.upmove < -20) - delta *= 2; - - // never take falling damage if completely underwater - if ( pm->ps.waterlevel == 3 ) - return; - - // reduce falling damage if there is standing water - if ( pm->ps.waterlevel == 2 ) - delta *= 0.25; - if ( pm->ps.waterlevel == 1 ) - delta *= 0.5; - - if ( delta < 1 ) - return; -/* - if (delta > 60) - printf("Far crashland: %f\n", delta); - else if (delta > 40) - printf("Medium crashland: %f\n", delta); - else if (delta > 4) - printf("Short crashland: %f\n", delta); -*/ - if (delta > 60) - { - /* - static const namestruct healthDamage("Health Damage"); - const SOUN* const soun = SOUN::GetSound(healthDamage); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); - }*/ - } - - if (delta > 3) // We need at least a short crashland to proc the sound effects: - { - bool splashSound = false; - - if (pm->hasWater) - { - - const float waterHeight = pm->waterHeight; - const float waterHeightSplash = waterHeight + pm->ps.halfExtents.y; - if (pm->ps.origin.z < waterHeightSplash) - { - splashSound = true; - } - - } - - - if (splashSound) - { - //Change this later----------------------------------- - /* - const namestruct ns("DefaultLandWater"); - const SOUN* const soun = SOUN::GetSound(ns); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundDatga->GetVolumeFloat() ); - }*/ - } - else - { - //Change this later--------------------------------- - /* - namestruct defaultCreature; - const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, SNDG::land); - if (sndg) - { - const namestruct& SOUNID = sndg->soundID; - const SOUN* const soun = SOUN::GetSound(SOUNID); - if (soun) - { - PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); - } - }*/ - } - } - - // create a local entity event to play the sound - - // SURF_NODAMAGE is used for bounce pads where you don't ever - // want to take damage or play a crunch sound - //if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) - { - /*if ( delta > 60 ) - PM_AddEvent( EV_FALL_FAR ); - else if ( delta > 40 ) - { - // this is a pain grunt, so don't play it if dead - if ( pm->ps->stats[STAT_HEALTH] > 0 ) - PM_AddEvent( EV_FALL_MEDIUM ); - } - else if ( delta > 7 ) - PM_AddEvent( EV_FALL_SHORT ); - else - PM_AddEvent( PM_FootstepForSurface() );*/ - } - - // start footstep cycle over - //pm->ps->bobCycle = 0; -} - -static void PM_GroundTrace( void ) -{ - Ogre::Vector3 point; - traceResults trace; - - /*point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25;*/ - point = pm->ps.origin; - point.z -= 0.25f; - - //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -0.25f, 0.0f), 0, pml.traceObj); - newtrace(&trace, pm->ps.origin, point, pm->ps.halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); - pml.groundTrace = trace; - - // do something corrective if the trace starts in a solid... - if ( trace.allsolid ) { - //std::cout << "ALL SOLID\n"; - if ( !PM_CorrectAllSolid(&trace) ){ - //std::cout << "Returning after correct all solid\n"; - return; - } - } - // if the trace didn't hit anything, we are in free fall - if ( trace.fraction == 1.0) - { - if(pm->ps.velocity.z > 50.0f && pm->ps.bSnap && pm->ps.speed > 1000.0f) - pm->ps.velocity.z = 50.0f; - if(pm->ps.snappingImplemented){ - if(pm->ps.bSnap && pm->ps.counter <= 0) - PM_GroundTraceMissed(); - } - - - - return; - } - else - { - //It hit something, so we are on the ground - pm->ps.bSnap = true; - - } - // check if getting thrown off the ground - //if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) - if (pm->ps.velocity.z > 0 && pm->ps.velocity.dotProduct(trace.planenormal) > 10.0f ) - { - //if ( pm->debugLevel ) - //Com_Printf("%i:kickoff\n", c_pmove); - - // go into jump animation - /*if ( pm->cmd.forwardmove >= 0 ) - { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } - else - { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - }*/ - if(!pm->ps.bSnap){ - pm->ps.groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = false; - pml.walking = false; - } - else - { - pml.groundPlane = true; - pml.walking = true; - } - return; - } - - - - - // slopes that are too steep will not be considered onground - //if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) - //std::cout << "MinWalkNormal" << trace.planenormal.z; - if (trace.planenormal.z < MIN_WALK_NORMAL) - { - //if ( pm->debugLevel ) - //Com_Printf("%i:steep\n", c_pmove); - - // FIXME: if they can't slide down the slope, let them - // walk (sharp crevices) - pm->ps.groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = true; - pml.walking = false; - return; - } - - pml.groundPlane = true; - pml.walking = true; - - // hitting solid ground will end a waterjump - /*if (pm->ps.pm_flags & PMF_TIME_WATERJUMP) - { - pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); - pm->ps->pm_time = 0; - }*/ - - if ( pm->ps.groundEntityNum == ENTITYNUM_NONE ) - { - // just hit the ground - /*if ( pm->debugLevel ) - Com_Printf("%i:Land\n", c_pmove);*/ - //bprintf("Land\n"); - - PM_CrashLand(); - - // don't do landing time if we were just going down a slope - //if ( pml.previous_velocity[2] < -200 ) - if (pml.previous_velocity.z < -200) - { - // don't allow another jump for a little while - //pm->ps->pm_flags |= PMF_TIME_LAND; - pm->ps.pm_time = 250; - } - } - - pm->ps.groundEntityNum = trace.entityNum; - - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - - //PM_AddTouchEnt( trace.entityNum ); -} - -void PM_AirMove() -{ - //int i; - Ogre::Vector3 wishvel; - float fmove, smove; - Ogre::Vector3 wishdir; - float wishspeed; - float scale; - playerMove::playercmd cmd; - //pm->ps.gravity = 800; - PM_Friction(); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - // set the movementDir so clients can rotate the legs for strafing - //PM_SetMovementDir(); - - // project moves down to flat plane - //pml.forward[2] = 0; - pml.forward.z = 0; //Z or Y? - //pml.right[2] = 0; - pml.right.z = 0; - //VectorNormalize (pml.forward); - VectorNormalize(pml.forward); - VectorNormalize(pml.right); - //VectorNormalize (pml.right); - - //for ( i = 0 ; i < 2 ; i++ ) - //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; - wishvel = pml.forward * fmove + pml.right * smove; - - //wishvel[2] = 0; - wishvel.z = 0; - - //VectorCopy (wishvel, wishdir); - wishdir = wishvel; - //wishspeed = VectorNormalize(wishdir); - wishspeed = VectorNormalize(wishdir); - - wishspeed *= scale; - - // not on ground, so little effect on velocity - PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); - - // we may have a ground plane that is very steep, even - // though we don't have a groundentity - // slide along the steep plane - if ( pml.groundPlane ) - PM_ClipVelocity (pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP ); - -/*#if 0 - //ZOID: If we are on the grapple, try stair-stepping - //this allows a player to use the grapple to pull himself - //over a ledge - if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) - PM_StepSlideMove ( qtrue ); - else - PM_SlideMove ( qtrue ); -#endif*/ - //std::cout << "Moving in the air" << pm->ps.velocity << "\n"; - - /*bprintf("%i ", */PM_StepSlideMove ( true )/* )*/; - - -} - -static void PM_NoclipMove( void ) -{ - float speed, drop, friction, control, newspeed; -// int i; - Ogre::Vector3 wishvel; - float fmove, smove; - Ogre::Vector3 wishdir; - float wishspeed; - float scale; - - //pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - - // friction - - //speed = VectorLength (pm->ps->velocity); - speed = pm->ps.velocity.length(); - if (speed < 1) - //VectorCopy (vec3_origin, pm->ps->velocity); - pm->ps.velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); - else - { - drop = 0; - - friction = pm_friction * 1.5f; // extra friction - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control * friction * pml.frametime; - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) - newspeed = 0; - newspeed /= speed; - - //VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); - pm->ps.velocity = pm->ps.velocity * newspeed; - } - - // accelerate - scale = PM_CmdScale( &pm->cmd ); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - //for (i=0 ; i<3 ; i++) - //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; - - wishvel = pml.forward * fmove + pml.right * smove; - //wishvel[2] += pm->cmd.upmove; - wishvel.z += pm->cmd.upmove; - - //VectorCopy (wishvel, wishdir); - wishdir = wishvel; - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - - PM_Accelerate( wishdir, wishspeed, pm_accelerate ); - - // move - //VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); - pm->ps.origin = pm->ps.origin + pm->ps.velocity * pml.frametime; -} - -static void PM_DropTimers( void ) -{ - // drop misc timing counter - if ( pm->ps.pm_time ) - { - if ( pml.msec >= pm->ps.pm_time ) - { - //pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps.pm_time = 0; - } - else - pm->ps.pm_time -= pml.msec; - } - - //bprintf("Time: %i\n", pm->ps.pm_time); - - // drop animation counter - /*if ( pm->ps->legsTimer > 0 ) - { - pm->ps->legsTimer -= pml.msec; - if ( pm->ps->legsTimer < 0 ) - pm->ps->legsTimer = 0; - } - - if ( pm->ps->torsoTimer > 0 ) - { - pm->ps->torsoTimer -= pml.msec; - if ( pm->ps->torsoTimer < 0 ) - pm->ps->torsoTimer = 0; - }*/ -} - -static void PM_FlyMove( void ) -{ - //int i; - Ogre::Vector3 wishvel; - float wishspeed; - Ogre::Vector3 wishdir; - float scale; - - // normal slowdown - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) - { - /*wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = 0;*/ - wishvel = Ogre::Vector3(0,0,0); - } - else - { - //for (i=0 ; i<3 ; i++) - //wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - wishvel = pml.forward * scale * pm->cmd.forwardmove + pml.right * scale * pm->cmd.rightmove; - - //wishvel[2] += scale * pm->cmd.upmove; - wishvel.z += /*6.35f * */pm->cmd.upmove * scale; - } - - //VectorCopy (wishvel, wishdir); - wishdir = wishvel; - - //wishspeed = VectorNormalize(wishdir); - wishspeed = VectorNormalize(wishdir); - - PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); - - PM_StepSlideMove( false ); -} - - -void PM_SetWaterLevel( playerMove* const pm ) -{ - Ogre::Vector3 point; - //int cont; - int sample1; - int sample2; - - // - // get waterlevel, accounting for ducking - // - - pm->ps.waterlevel = WL_DRYLAND; - pm->ps.watertype = 0; - - /*point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] + MINS_Z + 1; */ - point.x = pm->ps.origin.x; - point.y = pm->ps.origin.y; - point.z = pm->ps.origin.z + MINS_Z + 1; - - //cont = pm->pointcontents( point, pm->ps->clientNum ); - bool checkWater = (pml.hasWater && pml.waterHeight > point.z); - //if ( cont & MASK_WATER ) - if ( checkWater) - { - sample2 = /*pm->ps.viewheight*/DEFAULT_VIEWHEIGHT - MINS_Z; - sample1 = sample2 / 2; - - pm->ps.watertype = CONTENTS_WATER;//cont; - pm->ps.waterlevel = WL_ANKLE; - //point[2] = pm->ps->origin[2] + MINS_Z + sample1; - point.z = pm->ps.origin.z + MINS_Z + sample1; - checkWater = (pml.hasWater && pml.waterHeight > point.z); - //cont = pm->pointcontents (point, pm->ps->clientNum ); - //if ( cont & MASK_WATER ) - if (checkWater) - { - pm->ps.waterlevel = WL_WAIST; - //point[2] = pm->ps->origin[2] + MINS_Z + sample2; - point.z = pm->ps.origin.z + MINS_Z + sample2; - //cont = pm->pointcontents (point, pm->ps->clientNum ); - //if ( cont & MASK_WATER ) - checkWater = (pml.hasWater && pml.waterHeight > point.z); - if (checkWater ) - pm->ps.waterlevel = WL_UNDERWATER; - } - } -} - -void PmoveSingle (playerMove* const pmove) -{ - pmove->ps.counter--; - //pm = pmove; - - // Aedra doesn't support Q3-style VM traps D: //while(1); - - // this counter lets us debug movement problems with a journal - // by setting a conditional breakpoint fot the previous frame - //c_pmove++; - - // clear results - //pm->numtouch = 0; - pm->ps.watertype = 0; - pm->ps.waterlevel = WL_DRYLAND; - - //if ( pm->ps->stats[STAT_HEALTH] <= 0 ) - //pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies - - - // make sure walking button is clear if they are running, to avoid - // proxy no-footsteps cheats - //if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) - //pm->cmd.buttons &= ~BUTTON_WALKING; - - - // set the talk balloon flag - //if ( pm->cmd.buttons & BUTTON_TALK ) - //pm->ps->eFlags |= EF_TALK; - //else - //pm->ps->eFlags &= ~EF_TALK; - - // set the firing flag for continuous beam weapons - /*if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION - && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) - pm->ps->eFlags |= EF_FIRING; - else - pm->ps->eFlags &= ~EF_FIRING;*/ - - // clear the respawned flag if attack and use are cleared - /*if ( pm->ps->stats[STAT_HEALTH] > 0 && - !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) - pm->ps->pm_flags &= ~PMF_RESPAWNED;*/ - - // if talk button is down, dissallow all other input - // this is to prevent any possible intercept proxy from - // adding fake talk balloons - /*if ( pmove->cmd.buttons & BUTTON_TALK ) - { - // keep the talk button set tho for when the cmd.serverTime > 66 msec - // and the same cmd is used multiple times in Pmove - pmove->cmd.buttons = BUTTON_TALK; - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; - }*/ - - // clear all pmove local vars - memset (&pml, 0, sizeof(pml) ); - - // Aedra-specific code: - //pml.scene = global_lastscene; - - - // End Aedra-specific code - pml.hasWater = pmove->hasWater; - pml.isInterior = pmove->isInterior; - pml.waterHeight = pmove->waterHeight; - - // determine the time - pml.msec = pmove->cmd.serverTime - pm->ps.commandTime; - if ( pml.msec < 1 ) - pml.msec = 1; - else if ( pml.msec > 200 ) - pml.msec = 200; - - //pm->ps->commandTime = pmove->cmd.serverTime; - - // Commented out as a hack - pm->ps.commandTime = pmove->cmd.serverTime; - - // Handle state change procs: - if (pm->cmd.activating != pm->cmd.lastActivatingState) - { - if (!pm->cmd.lastActivatingState && pm->cmd.activating) - pm->cmd.procActivating = playerMove::playercmd::KEYDOWN; - else - pm->cmd.procActivating = playerMove::playercmd::KEYUP; - } - else - { - pm->cmd.procActivating = playerMove::playercmd::NO_CHANGE; - } - pm->cmd.lastActivatingState = pm->cmd.activating; - - if (pm->cmd.dropping != pm->cmd.lastDroppingState) - { - if (!pm->cmd.lastDroppingState && pm->cmd.dropping) - pm->cmd.procDropping = playerMove::playercmd::KEYDOWN; - else - pm->cmd.procDropping = playerMove::playercmd::KEYUP; - } - else - { - pm->cmd.procDropping = playerMove::playercmd::NO_CHANGE; - } - pm->cmd.lastDroppingState = pm->cmd.dropping; - - // save old org in case we get stuck - //VectorCopy (pm->ps->origin, pml.previous_origin); - pml.previous_origin = pm->ps.origin; - - // Copy over the lastframe origin - pmove->ps.lastframe_origin = pmove->ps.origin; - - //pmove->ps.lastframe_origin = pmove->ps.origin; - - // save old velocity for crashlanding - //VectorCopy (pm->ps->velocity, pml.previous_velocity); - pml.previous_velocity = pm->ps.velocity; - - pml.frametime = pml.msec * 0.001f; - - // update the viewangles - //PM_UpdateViewAngles( &(pm->ps), &(pm->cmd) ); - - AngleVectors (pm->ps.viewangles, &(pml.forward), &(pml.right), &(pml.up) ); - - //if ( pm->cmd.upmove < 10 ) - // not holding jump - //pm->ps->pm_flags &= ~PMF_JUMP_HELD; - - // decide if backpedaling animations should be used - /*if ( pm->cmd.forwardmove < 0 ) - pm->ps->pm_flags |= PMF_BACKWARDS_RUN; - else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) - pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN;*/ - - /*if ( pm->ps->pm_type >= PM_DEAD ) - { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - }*/ - - if ( pm->ps.move_type == PM_SPECTATOR ) - { - - //PM_CheckDuck (); - PM_FlyMove (); - PM_DropTimers (); - return; - } - - if ( pm->ps.move_type == PM_NOCLIP ) - { - - PM_NoclipMove (); - PM_DropTimers (); - return; - } - - if (pm->ps.move_type == PM_FREEZE){ - - return; // no movement at all - - } - - if ( pm->ps.move_type == PM_INTERMISSION || pm->ps.move_type == PM_SPINTERMISSION){ - return; // no movement at all - } - - // set watertype, and waterlevel - PM_SetWaterLevel(pmove); - pml.previous_waterlevel = pmove->ps.waterlevel; - - // set mins, maxs, and viewheight - //PM_CheckDuck (); - - // set groundentity - PM_GroundTrace(); - - /*if ( pm->ps->pm_type == PM_DEAD ) - PM_DeadMove (); - - PM_DropTimers();*/ - - PM_DropTimers(); - -/*#ifdef MISSIONPACK - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_InvulnerabilityMove(); - } else -#endif*/ - /*if ( pm->ps->powerups[PW_FLIGHT] ) - // flight powerup doesn't allow jump and has different friction - PM_FlyMove(); - else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) - { - PM_GrappleMove(); - // We can wiggle a bit - PM_AirMove(); - } - else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) - PM_WaterJumpMove();*/ - if ( pmove->ps.waterlevel > 1 ) - // swimming - PM_WaterMove(pmove); - else if ( pml.walking ) - { - - // walking on ground - PM_WalkMove(pmove); - //bprintf("WalkMove\n"); - } - else - { - // airborne - //std::cout << "AIRMOVE\n"; - PM_AirMove(); - //bprintf("AirMove\n"); - } - - //PM_Animate(); - - // set groundentity, watertype, and waterlevel - PM_GroundTrace(); - PM_SetWaterLevel(pmove); - - // weapons - /*PM_Weapon(); - - // torso animation - PM_TorsoAnimation(); - - // footstep events / legs animations - PM_Footsteps(); - - // entering / leaving water splashes - PM_WaterEvents(); - - // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity );*/ -} - -void Ext_UpdateViewAngles(playerMove* const pm) -{ - playerMove::playerStruct* const ps = &(pm->ps); - playerMove::playercmd* const cmd = &(pm->cmd); - PM_UpdateViewAngles(ps, cmd); -} - -void Pmove (playerMove* const pmove) -{ - // warning: unused variable ‘fmove’ - //int fmove = pmove->cmd.forwardmove; - - pm = pmove; - - int finalTime; - - finalTime = pmove->cmd.serverTime; - - pmove->ps.commandTime = 40; - - if ( finalTime < pmove->ps.commandTime ) - return; // should not happen - - if ( finalTime > pmove->ps.commandTime + 1000 ) - pmove->ps.commandTime = finalTime - 1000; - - pmove->ps.pmove_framecount = (pmove->ps.pmove_framecount + 1) & ( (1 << PS_PMOVEFRAMECOUNTBITS) - 1); - - // chop the move up if it is too long, to prevent framerate - // dependent behavior - while ( pmove->ps.commandTime != finalTime ) - { - int msec; - - msec = finalTime - pmove->ps.commandTime; - - if ( pmove->pmove_fixed ) - { - if ( msec > pmove->pmove_msec ) - msec = pmove->pmove_msec; - } - else - { - if ( msec > 66 ) - msec = 66; - } - - pmove->cmd.serverTime = pmove->ps.commandTime + msec; - - if (pmove->isInterior) - { - PmoveSingle( pmove ); - } - else - { - PmoveSingle( pmove ); - /* - std::map::const_iterator it = ExtCellLookup.find(PositionToCell(pmove->ps.origin) ); - if (it != ExtCellLookup.end() ) - { - pmove->traceObj->incellptr = it->second; - }*/ - } - - //if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) - //pmove->cmd.upmove = 20; - } - - //pmove->ps.last_compute_time = GetTimeQPC(); - //pmove->ps.lerp_multiplier = (pmove->ps.origin - pmove->ps.lastframe_origin);// * (1.000 / 31.0); - - //PM_CheckStuck(); - -} - - diff --git a/libs/openengine/bullet/pmove.h b/libs/openengine/bullet/pmove.h deleted file mode 100644 index fa303184e..000000000 --- a/libs/openengine/bullet/pmove.h +++ /dev/null @@ -1,200 +0,0 @@ -#ifndef OENGINE_BULLET_PMOVE_H -#define OENGINE_BULLET_PMOVE_H -/* -This source file is a *modified* version of various header files from the Quake 3 Arena source code, -which was released under the GNU GPL (v2) in 2005. -Quake 3 Arena is copyright (C) 1999-2005 Id Software, Inc. -*/ - -#include -#include -#include "trace.h" -#include "physic.hpp" - -#include - -//#include "GameMath.h" -//#include "GameTime.h" - -// Forwards-declare it! - -/*#ifndef COMPILING_PMOVE -#include "Scene.h" -extern SceneInstance* global_lastscene; -#endif*/ - -static const Ogre::Vector3 halfExtentsDefault(14.64f * 2, 14.24f * 2, 33.25f * 2); - -#define MAX_CLIP_PLANES 5 -#define OVERCLIP 1.001f -//#define STEPSIZE 18 // 18 is way too much -#define STEPSIZE (9) -#ifndef M_PI - #define M_PI 3.14159265358979323846f -#endif -#define YAW 0 -#define PITCH /*1*/2 -#define ROLL /*2*/1 -#define SHORT2ANGLE(x) ( (x) * (360.0f / 65536.0f) ) -#define ANGLE2SHORT(x) ( (const short)( (x) / (360.0f / 65536.0f) ) ) -#define GENTITYNUM_BITS 10 // don't need to send any more -#define MAX_GENTITIES (1 << GENTITYNUM_BITS) -#define ENTITYNUM_NONE (MAX_GENTITIES - 1) -#define ENTITYNUM_WORLD (MAX_GENTITIES - 2) -#define MIN_WALK_NORMAL .7f // can't walk on very steep slopes -#define PS_PMOVEFRAMECOUNTBITS 6 -#define MINS_Z -24 -#define DEFAULT_VIEWHEIGHT 26 -#define CROUCH_VIEWHEIGHT 12 -#define DEAD_VIEWHEIGHT (-16) -#define CONTENTS_SOLID 1 // an eye is never valid in a solid -#define CONTENTS_LAVA 8 -#define CONTENTS_SLIME 16 -#define CONTENTS_WATER 32 -#define CONTENTS_FOG 64 -static const float pm_accelerate = 10.0f; -static const float pm_stopspeed = 100.0f; -static const float pm_friction = 12.0f; -static const float pm_flightfriction = 3.0f; -static const float pm_waterfriction = 1.0f; -static const float pm_airaccelerate = 1.0f; -static const float pm_swimScale = 0.50f; -static const float pm_duckScale = 0.25f; -static const float pm_flyaccelerate = 8.0f; -static const float pm_wateraccelerate = 4.0f; - -enum pmtype_t -{ - PM_NORMAL, // can accelerate and turn - PM_NOCLIP, // noclip movement - PM_SPECTATOR, // still run into walls - PM_DEAD, // no acceleration or turning, but free falling - PM_FREEZE, // stuck in place with no control - PM_INTERMISSION, // no movement or status bar - PM_SPINTERMISSION // no movement or status bar -}; - -enum waterlevel_t -{ - WL_DRYLAND = 0, - WL_ANKLE, - WL_WAIST, - WL_UNDERWATER -}; - - -//#include "bprintf.h" - -struct playerMove -{ - struct playerStruct - { - playerStruct() : gravity(800.0f), speed(480.0f), jump_velocity(270), pmove_framecount(20), groundEntityNum(ENTITYNUM_NONE), commandTime(40), move_type(PM_NOCLIP), pm_time(0), snappingImplemented(true), bSnap(false), counter(-1), halfExtents(halfExtentsDefault) - { - origin = Ogre::Vector3(0.0f, 0.0f, 0.0f); - velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); - - viewangles = Ogre::Vector3(0.0f, 0.0f, 0.0f); - - delta_angles[0] = delta_angles[1] = delta_angles[2] = 0; - - lastframe_origin.x = lastframe_origin.y = lastframe_origin.z = 0; - lerp_multiplier.x = lerp_multiplier.y = lerp_multiplier.z = 0; - } - - inline void SpeedUp(void) - { - //printf("speed up to: %f\n", speed); - speed *= 1.25f; - } - - inline void SpeedDown(void) - { - //printf("speed down to %f\n", speed); - speed /= 1.25f; - } - - Ogre::Vector3 velocity; - Ogre::Vector3 origin; - Ogre::Vector3 halfExtents; - bool bSnap; - bool snappingImplemented; - int counter; - float gravity; // default = 800 - float speed; // default = 320 - float jump_velocity; //default = 270 - - int commandTime; // the time at which this command was issued (in milliseconds) - - int pm_time; - - Ogre::Vector3 viewangles; - - int groundEntityNum; - - int pmove_framecount; - - int watertype; - waterlevel_t waterlevel; - - signed short delta_angles[3]; - - pmtype_t move_type; - - float last_compute_time; - Ogre::Vector3 lastframe_origin; - Ogre::Vector3 lerp_multiplier; - } ps; - - struct playercmd - { - enum CMDstateChange - { - NO_CHANGE, - KEYDOWN, - KEYUP - }; - - playercmd() : forwardmove(0), rightmove(0), upmove(0), serverTime(50), ducking(false), - activating(false), lastActivatingState(false), procActivating(NO_CHANGE), - dropping(false), lastDroppingState(false), procDropping(NO_CHANGE) - { - angles[0] = angles[1] = angles[2] = 0; - } - - int serverTime; - - short angles[3]; - - signed char forwardmove; - signed char rightmove; - signed char upmove; - - bool ducking; - bool activating; // if the user is holding down the activate button - bool dropping; // if the user is dropping an item - - bool lastActivatingState; - bool lastDroppingState; - - CMDstateChange procActivating; - CMDstateChange procDropping; - } cmd; - - playerMove() : msec(50), pmove_fixed(false), pmove_msec(50), waterHeight(0), isInterior(true), hasWater(false) - { - } - - int msec; - int pmove_msec; - bool pmove_fixed; - int waterHeight; - bool hasWater; - bool isInterior; - OEngine::Physic::PhysicEngine* mEngine; -}; - -void Pmove (playerMove* const pmove); -void Ext_UpdateViewAngles(playerMove* const pm); -void AngleVectors( const Ogre::Vector3& angles, Ogre::Vector3* const forward, Ogre::Vector3* const right, Ogre::Vector3* const up) ; -#endif diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 9f5398574..b6649199d 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -1,196 +1,48 @@ #include "trace.h" - - #include +#include +#include +#include "physic.hpp" - - - - -void newtrace(traceResults* const results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, const float rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass) //Traceobj was a Aedra Object +enum traceWorldType { - //static float lastyaw = 0.0f; - //static float lastpitch = 0.0f; - //if (!traceobj) - // return; + collisionWorldTrace = 1, + pickWorldTrace = 2, + bothWorldTrace = collisionWorldTrace | pickWorldTrace +}; - //if (!traceobj->incellptr) - // return; - - - const Ogre::Vector3 rayDir = end - start; - - - - - - - - NewPhysTraceResults out; - //std::cout << "Starting trace\n"; - //Ogre::Vector3 startReplace = Ogre::Vector3(650,950, 45); - //Ogre::Vector3 endReplace = startReplace; - //endReplace.z -= .25; - - const bool hasHit = NewPhysicsTrace(&out, start, end, BBHalfExtents, Ogre::Vector3(0.0f, 0.0f, 0.0f), isInterior, enginePass); - - if (out.fraction < 0.001f) - results->startsolid = true; - else - results->startsolid = false; - - - //results->allsolid = out.startSolid; - - // If outside and underground, we're solid - /*if (isInterior) - { - const Ogre::Vector3 height = GetGroundPosition(start, CellCoords(traceCell->data->gridX, traceCell->data->gridY) ); - if (start.yPos - height.yPos < (-2.0f * BBHalfExtents.yPos) ) - { - results->allsolid = true; - } - else - results->allsolid = false; - }*/ - - // If inside and out of the tree, we're solid - //else - //{ - results->allsolid = out.startSolid; - //std::cout << "allsolid" << results->allsolid << "\n"; - //} - - if (!hasHit) - { - results->endpos = end; - results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); - results->entityNum = ENTITYNUM_NONE; - results->fraction = 1.0f; - } - else - { - results->fraction = out.fraction; - results->planenormal = out.hitNormal; - results->endpos = rayDir * results->fraction + start; - results->entityNum = ENTITYNUM_WORLD; - /*bprintf("Start: (%f, %f, %f) End: (%f, %f, %f) TraceDir: (%f, %f, %f) HitNormal: (%f, %f, %f) Fraction: %f Hitpos: (%f, %f, %f) CompensatedHitpos: (%f, %f, %f)\n", - start.xPos, start.yPos, start.zPos, - end.xPos, end.yPos, end.zPos, - rayDir.xPos, rayDir.yPos, rayDir.zPos, - results->planenormal.xPos, results->planenormal.yPos, results->planenormal.zPos, results->fraction, - out.endPos.xPos, out.endPos.yPos, out.endPos.zPos, - results->endpos.xPos, results->endpos.yPos, results->endpos.zPos);*/ - } -} - - - -template -const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, - const Ogre::Vector3& BBHalfExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass) +void newtrace(traceResults *results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine *enginePass) //Traceobj was a Aedra Object { - //if (!traceobj->incellptr) - // return false; - //if(enginePass->dynamicsWorld->getCollisionObjectArray().at(60)->getCollisionShape()->isConvex()) - // std::cout << "It's convex\n"; - - - - const btVector3 btstart(start.x, start.y, start.z + BBHalfExtents.z); - const btVector3 btend(end.x, end.y, end.z + BBHalfExtents.z); - const btQuaternion btrot(rotation.y, rotation.x, rotation.z); //y, x, z + const btVector3 btstart(start.x, start.y, start.z + BBHalfExtents.z); + const btVector3 btend(end.x, end.y, end.z + BBHalfExtents.z); + const btQuaternion btrot(0.0f, 0.0f, 0.0f); //y, x, z const btBoxShape newshape(btVector3(BBHalfExtents.x, BBHalfExtents.y, BBHalfExtents.z)); - //const btCapsuleShapeZ newshape(BBHalfExtents.x, BBHalfExtents.z * 2 - BBHalfExtents.x * 2); - const btTransform from(btrot, btstart); - const btTransform to(btrot, btend); + //const btCapsuleShapeZ newshape(BBHalfExtents.x, BBHalfExtents.z * 2 - BBHalfExtents.x * 2); + const btTransform from(btrot, btstart); + const btTransform to(btrot, btend); - // warning: unused variable ... - /* - float x = from.getOrigin().getX(); - float y = from.getOrigin().getY(); - float z = from.getOrigin().getZ(); - float x2 = to.getOrigin().getX(); - float y2 = to.getOrigin().getY(); - float z2 = to.getOrigin().getZ(); - */ - - //std::cout << "BtFrom: " << x << "," << y << "," << z << "\n"; - //std::cout << "BtTo: " << x2 << "," << y2 << "," << z2 << "\n"; - //std::cout << "BtTo: " << to.getOrigin().getX() << "," << to.getOrigin().getY() << "," << to.getOrigin().getZ() << "\n"; + btCollisionWorld::ClosestConvexResultCallback newTraceCallback(btstart, btend); + newTraceCallback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_Raycasting; + enginePass->dynamicsWorld->convexSweepTest(&newshape, from, to, newTraceCallback); - btCollisionWorld::ClosestConvexResultCallback - newTraceCallback(btstart, btend); - - newTraceCallback.m_collisionFilterMask = (traceType == collisionWorldTrace) ? Only_Collision : Only_Pickup; - - - enginePass->dynamicsWorld->convexSweepTest(&newshape, from, to, newTraceCallback); - //newTraceCallback. - - - //std::cout << "NUM: " << enginePass->dynamicsWorld->getNumCollisionObjects() << "\n"; - - // Copy the hit data over to our trace results struct: - out->fraction = newTraceCallback.m_closestHitFraction; - - Ogre::Vector3& outhitnormal = out->hitNormal; - const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; - - outhitnormal.x = tracehitnormal.x(); - outhitnormal.y = tracehitnormal.y(); - outhitnormal.z = tracehitnormal.z(); - - Ogre::Vector3& outhitpos = out->endPos; - const btVector3& tracehitpos = newTraceCallback.m_hitPointWorld; - - outhitpos.x = tracehitpos.x(); - outhitpos.y = tracehitpos.y(); - outhitpos.z= tracehitpos.z(); - - // StartSolid test: - { - out->startSolid = false; - //btCollisionObject collision; - //collision.setCollisionShape(const_cast(&newshape) ); - - //CustomContactCallback crb; - - //world.world->contactTest(&collision, crb); - //out->startSolid = crb.hit; - - // If outside and underground, we're solid - if (!isInterior) //Check if we are interior - { - } - - // If inside and out of the tree, we're solid - else - { - btVector3 aabbMin, aabbMax; - enginePass->broadphase->getBroadphaseAabb(aabbMin, aabbMax); - //std::cout << "AABBMIN" << aabbMin.getX() <<"," <startSolid = true; - } - } - } - - const bool hasHit = newTraceCallback.hasHit(); - - - - - return hasHit; + // Copy the hit data over to our trace results struct: + if(newTraceCallback.hasHit()) + { + const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; + results->fraction = newTraceCallback.m_closestHitFraction; + results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + results->endpos = (end-start)*results->fraction + start; + } + else + { + results->endpos = end; + results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + results->fraction = 1.0f; + } } diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 1bfe0c717..f484497d9 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -1,60 +1,26 @@ #ifndef OENGINE_BULLET_TRACE_H #define OENGINE_BULLET_TRACE_H - -#include -#include -#include -#include -#include "pmove.h" +#include -enum traceWorldType +namespace OEngine { - collisionWorldTrace = 1, - pickWorldTrace = 2, - bothWorldTrace = collisionWorldTrace | pickWorldTrace -}; + namespace Physic + { + class PhysicEngine; + } +} -enum collaborativePhysicsType -{ - No_Physics = 0, // Both are empty (example: statics you can walk through, like tall grass) - Only_Collision = 1, // This object only has collision physics but no pickup physics (example: statics) - Only_Pickup = 2, // This object only has pickup physics but no collision physics (example: items dropped on the ground) - Both_Physics = 3 // This object has both kinds of physics (example: activators) -}; -struct NewPhysTraceResults -{ - Ogre::Vector3 endPos; - Ogre::Vector3 hitNormal; - float fraction; - bool startSolid; - //const Object* hitObj; -}; struct traceResults { - Ogre::Vector3 endpos; - Ogre::Vector3 planenormal; + Ogre::Vector3 endpos; + Ogre::Vector3 planenormal; - float fraction; - - int surfaceFlags; - int contents; - int entityNum; - - bool allsolid; - bool startsolid; + float fraction; }; - - -template -const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); -//template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); -//template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); - -void newtrace(traceResults* const results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const float rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); - +void newtrace(traceResults *results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); #endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index acb4ed9df..c9b561400 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -1,11 +1,25 @@ -#include -#include -#include - #include "manager.hpp" +#include +#include + +#include + using namespace OEngine::GUI; +/* + * As of MyGUI 3.2.0, MyGUI::OgreDataManager::isDataExist is unnecessarily complex + * this override fixes the resulting performance issue. + */ +class FixedOgreDataManager : public MyGUI::OgreDataManager +{ +public: + bool isDataExist(const std::string& _name) + { + return Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup (_name); + } +}; + void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) { assert(wnd); @@ -25,26 +39,50 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool if(!logDir.empty()) theLogFile.insert(0, logDir); - // Set up OGRE platform. We might make this more generic later. - mPlatform = new OgrePlatform(); - LogManager::getInstance().setSTDOutputEnabled(logging); - mPlatform->initialise(wnd, mgr, "General", theLogFile); + // Set up OGRE platform (bypassing OgrePlatform). We might make this more generic later. + mLogManager = new LogManager(); + mRenderManager = new OgreRenderManager(); + mDataManager = new FixedOgreDataManager(); + LogManager::getInstance().setSTDOutputEnabled(logging); + + if (!theLogFile.empty()) + LogManager::getInstance().createDefaultSource(theLogFile); + + mRenderManager->initialise(wnd, mgr); + mDataManager->initialise("General"); // Create GUI mGui = new Gui(); - mGui->initialise("core.xml"); + mGui->initialise(""); +} + +void MyGUIManager::updateWindow (Ogre::RenderWindow *wnd) +{ + mRenderManager->setRenderWindow (wnd); + mRenderManager->setActiveViewport(0); } void MyGUIManager::shutdown() { mGui->shutdown (); delete mGui; - if(mPlatform) + if(mRenderManager) { - mPlatform->shutdown(); - delete mPlatform; + mRenderManager->shutdown(); + delete mRenderManager; + mRenderManager = NULL; + } + if(mDataManager) + { + mDataManager->shutdown(); + delete mDataManager; + mDataManager = NULL; + } + if (mLogManager) + { + delete mLogManager; + mLogManager = NULL; } mGui = NULL; - mPlatform = NULL; } diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp index 1ec2e2fcf..16673ef98 100644 --- a/libs/openengine/gui/manager.hpp +++ b/libs/openengine/gui/manager.hpp @@ -1,10 +1,14 @@ #ifndef OENGINE_MYGUI_MANAGER_H #define OENGINE_MYGUI_MANAGER_H +#include + namespace MyGUI { - class OgrePlatform; class Gui; + class LogManager; + class OgreDataManager; + class OgreRenderManager; } namespace Ogre @@ -18,12 +22,15 @@ namespace GUI { class MyGUIManager { - MyGUI::OgrePlatform *mPlatform; MyGUI::Gui *mGui; + MyGUI::LogManager* mLogManager; + MyGUI::OgreDataManager* mDataManager; + MyGUI::OgreRenderManager* mRenderManager; Ogre::SceneManager* mSceneMgr; + public: - MyGUIManager() : mPlatform(NULL), mGui(NULL) {} + MyGUIManager() : mLogManager(NULL), mDataManager(NULL), mRenderManager(NULL), mGui(NULL) {} MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) { setup(wnd,mgr,logging, logDir); @@ -33,6 +40,8 @@ namespace GUI shutdown(); } + void updateWindow (Ogre::RenderWindow* wnd); + void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); void shutdown(); diff --git a/libs/openengine/ogre/atlas.cpp b/libs/openengine/ogre/atlas.cpp deleted file mode 100644 index 01b84afab..000000000 --- a/libs/openengine/ogre/atlas.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "atlas.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Ogre; -using namespace OEngine::Render; - -void Atlas::createFromFile (const std::string& filename, const std::string& textureName, const std::string& texturePrefix) -{ - ConfigFile file; - file.load(filename, ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, "\t:=", true); - - Root* root = Ogre::Root::getSingletonPtr(); - - SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); - Camera* camera = sceneMgr->createCamera("AtlasCamera"); - - int width = StringConverter::parseInt(file.getSetting("size_x", "settings")); - int height = StringConverter::parseInt(file.getSetting("size_y", "settings")); - - std::vector rectangles; - int i = 0; - - ConfigFile::SectionIterator seci = file.getSectionIterator(); - while (seci.hasMoreElements()) - { - Ogre::String sectionName = seci.peekNextKey(); - seci.getNext(); - - if (sectionName == "settings" || sectionName == "") - continue; - - MaterialPtr material = MaterialManager::getSingleton().create("AtlasMaterial" + StringConverter::toString(i), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - material->getTechnique(0)->getPass(0)->setLightingEnabled(false); - material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(texturePrefix + sectionName); - tus->setTextureBorderColour(ColourValue(0, 0, 0, 0)); - - Rectangle2D* rect = new Rectangle2D(true); - rect->setMaterial("AtlasMaterial" + StringConverter::toString(i)); - rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND); - - int x = StringConverter::parseInt(file.getSetting("x", sectionName)); - int y = StringConverter::parseInt(file.getSetting("y", sectionName)); - - TexturePtr texture = TextureManager::getSingleton().getByName(texturePrefix + sectionName); - if (texture.isNull()) - { - std::cerr << "OEngine::Render::Atlas: Can't find texture " << texturePrefix + sectionName << ", skipping..." << std::endl; - continue; - } - int textureWidth = texture->getWidth(); - int textureHeight = texture->getHeight(); - - float left = x/float(width) * 2 - 1; - float top = (1-(y/float(height))) * 2 - 1; - float right = ((x+textureWidth))/float(width) * 2 - 1; - float bottom = (1-((y+textureHeight)/float(height))) * 2 - 1; - rect->setCorners(left, top, right, bottom); - - // Use infinite AAB to always stay visible - AxisAlignedBox aabInf; - aabInf.setInfinite(); - rect->setBoundingBox(aabInf); - - // Attach background to the scene - SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(rect); - - rectangles.push_back(rect); - ++i; - } - - TexturePtr destTexture = TextureManager::getSingleton().createManual( - textureName, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - width, height, - 0, - PF_FLOAT16_RGBA, - TU_RENDERTARGET); - - RenderTarget* rtt = destTexture->getBuffer()->getRenderTarget(); - rtt->setAutoUpdated(false); - Viewport* vp = rtt->addViewport(camera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setBackgroundColour(ColourValue(0,0,0,0)); - - rtt->update(); - - // remove all the junk we've created - for (std::vector::iterator it=rectangles.begin(); - it!=rectangles.end(); ++it) - { - delete (*it); - } - while (i > 0) - { - MaterialManager::getSingleton().remove("AtlasMaterial" + StringConverter::toString(i-1)); - --i; - } - root->destroySceneManager(sceneMgr); -} diff --git a/libs/openengine/ogre/atlas.hpp b/libs/openengine/ogre/atlas.hpp deleted file mode 100644 index 5dcd409ca..000000000 --- a/libs/openengine/ogre/atlas.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef OENGINE_OGRE_ATLAS_HPP -#define OENGINE_OGRE_ATLAS_HPP - -#include - -namespace OEngine -{ -namespace Render -{ - - /// \brief Creates a texture atlas at runtime - class Atlas - { - public: - /** - * @param absolute path to file that specifies layout of the texture (positions of the textures it contains) - * @param name of the destination texture to save to (in memory) - * @param texture directory prefix - */ - static void createFromFile (const std::string& filename, const std::string& textureName, const std::string& texturePrefix="textures\\"); - }; - -} -} - -#endif - diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp deleted file mode 100644 index 11fd5eea6..000000000 --- a/libs/openengine/ogre/imagerotate.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "imagerotate.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Ogre; -using namespace OEngine::Render; - -void ImageRotate::rotate(const std::string& sourceImage, const std::string& destImage, const float angle) -{ - Root* root = Ogre::Root::getSingletonPtr(); - - SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); - Camera* camera = sceneMgr->createCamera("ImageRotateCamera"); - - MaterialPtr material = MaterialManager::getSingleton().create("ImageRotateMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - material->getTechnique(0)->getPass(0)->setLightingEnabled(false); - material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(sourceImage); - Degree deg(angle); - tus->setTextureRotate(Radian(deg.valueRadians())); - tus->setTextureAddressingMode(TextureUnitState::TAM_BORDER); - tus->setTextureBorderColour(ColourValue(0, 0, 0, 0)); - - Rectangle2D* rect = new Rectangle2D(true); - rect->setCorners(-1.0, 1.0, 1.0, -1.0); - rect->setMaterial("ImageRotateMaterial"); - // Render the background before everything else - rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND); - - // Use infinite AAB to always stay visible - AxisAlignedBox aabInf; - aabInf.setInfinite(); - rect->setBoundingBox(aabInf); - - // Attach background to the scene - SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(rect); - - // retrieve image width and height - TexturePtr sourceTexture = TextureManager::getSingleton().getByName(sourceImage); - unsigned int width = sourceTexture->getWidth(); - unsigned int height = sourceTexture->getHeight(); - - TexturePtr destTexture = TextureManager::getSingleton().createManual( - destImage, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - width, height, - 0, - PF_FLOAT16_RGBA, - TU_RENDERTARGET); - - RenderTarget* rtt = destTexture->getBuffer()->getRenderTarget(); - rtt->setAutoUpdated(false); - Viewport* vp = rtt->addViewport(camera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setBackgroundColour(ColourValue(0,0,0,0)); - - rtt->update(); - - // remove all the junk we've created - MaterialManager::getSingleton().remove("ImageRotateMaterial"); - root->destroySceneManager(sceneMgr); - delete rect; -} diff --git a/libs/openengine/ogre/imagerotate.hpp b/libs/openengine/ogre/imagerotate.hpp deleted file mode 100644 index a3f6d662f..000000000 --- a/libs/openengine/ogre/imagerotate.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef OENGINE_OGRE_IMAGEROTATE_HPP -#define OENGINE_OGRE_IMAGEROTATE_HPP - -#include - -namespace OEngine -{ -namespace Render -{ - - /// Rotate an image by certain degrees and save as file, uses the GPU - /// Make sure Ogre Root is initialised before calling - class ImageRotate - { - public: - /** - * @param source image (file name - has to exist in an resource group) - * @param name of the destination texture to save to (in memory) - * @param angle in degrees to turn - */ - static void rotate(const std::string& sourceImage, const std::string& destImage, const float angle); - }; - -} -} - -#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 87ebe1139..c4f35e087 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -192,6 +192,7 @@ void OgreRenderer::configure(const std::string &logPath, pluginDir = absPluginPath.string(); Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); @@ -204,16 +205,45 @@ void OgreRenderer::configure(const std::string &logPath, rs->setConfigOption ("RTT Preferred Mode", rttMode); } +void OgreRenderer::recreateWindow(const std::string &title, const WindowSettings &settings) +{ + Ogre::ColourValue viewportBG = mView->getBackgroundColour(); + + mRoot->destroyRenderTarget(mWindow); + NameValuePairList params; + params.insert(std::make_pair("title", title)); + params.insert(std::make_pair("FSAA", settings.fsaa)); + params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); + + mWindow = mRoot->createRenderWindow(title, settings.window_x, settings.window_y, settings.fullscreen, ¶ms); + + // Create one viewport, entire window + mView = mWindow->addViewport(mCamera); + mView->setBackgroundColour(viewportBG); + + adjustViewport(); +} + void OgreRenderer::createWindow(const std::string &title, const WindowSettings& settings) { assert(mRoot); mRoot->initialise(false); + // create a hidden 1x1 background window to keep resources when recreating the secondary (real) window + NameValuePairList params_; + params_.insert(std::make_pair("title", title)); + params_.insert(std::make_pair("FSAA", "0")); + params_.insert(std::make_pair("vsync", "false")); + params_.insert(std::make_pair("hidden", "true")); + Ogre::RenderWindow* hiddenWindow = mRoot->createRenderWindow("InactiveHidden", 1, 1, false, ¶ms_); + hiddenWindow->setActive(false); + NameValuePairList params; params.insert(std::make_pair("title", title)); params.insert(std::make_pair("FSAA", settings.fsaa)); params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); + mWindow = mRoot->createRenderWindow(title, settings.window_x, settings.window_y, settings.fullscreen, ¶ms); // create the semi-transparent black background texture used by the GUI. @@ -226,7 +256,7 @@ void OgreRenderer::createWindow(const std::string &title, const WindowSettings& 1, 1, 0, Ogre::PF_A8R8G8B8, - Ogre::TU_DYNAMIC_WRITE_ONLY); + Ogre::TU_WRITE_ONLY); } void OgreRenderer::createScene(const std::string& camName, float fov, float nearClip) @@ -258,12 +288,12 @@ void OgreRenderer::adjustViewport() void OgreRenderer::setWindowEventListener(Ogre::WindowEventListener* listener) { - Ogre::WindowEventUtilities::addWindowEventListener(mWindow, listener); + Ogre::WindowEventUtilities::addWindowEventListener(mWindow, listener); } void OgreRenderer::removeWindowEventListener(Ogre::WindowEventListener* listener) { - Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, listener); + Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, listener); } void OgreRenderer::setFov(float fov) diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index a8788dfca..251dc9c54 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -66,6 +66,7 @@ namespace OEngine #endif class Fader; + class OgreRenderer { #if defined(__APPLE__) && !defined(__LP64__) @@ -138,6 +139,8 @@ namespace OEngine /// Create a window with the given title void createWindow(const std::string &title, const WindowSettings& settings); + void recreateWindow (const std::string &title, const WindowSettings& settings); + /// Set up the scene manager, camera and viewport void createScene(const std::string& camName="Camera",// Camera name float fov=55, // Field of view angle diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index c6b43a45d..30e7b9e1e 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -24,7 +24,8 @@ namespace Render vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); vp->setShadowsEnabled(false); vp->setMaterialScheme("selectionbuffer"); - vp->setVisibilityMask (visibilityFlags); + if (visibilityFlags != 0) + vp->setVisibilityMask (visibilityFlags); mRenderTarget->setActive(true); mRenderTarget->setAutoUpdated (false); diff --git a/readme.txt b/readme.txt index 21ae85530..228278a91 100644 --- a/readme.txt +++ b/readme.txt @@ -3,13 +3,13 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.20.0 +Version: 0.21.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org Font Licenses: EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) -VeraMono.ttf: custom (see Bitstream Vera License.txt for more information) +DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information) @@ -94,6 +94,40 @@ Allowed options: CHANGELOG +0.21.0 + +Bug #253: Dialogs don't work for Russian version of Morrowind +Bug #267: Activating creatures without dialogue can still activate the dialogue GUI +Bug #354: True flickering lights +Bug #386: The main menu's first entry is wrong (in french) +Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations +Bug #495: Activation Range +Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned +Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available +Bug #500: Disposition for most NPCs is 0/100 +Bug #501: Getdisposition command wrongly returns base disposition +Bug #506: Journal UI doesn't update anymore +Bug #507: EnableRestMenu is not a valid command - change it to EnableRest +Bug #508: Crash in Ald Daedroth Shrine +Bug #517: Wrong price calculation when untrading an item +Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin +Bug #524: Beast races are able to wear shoes +Bug #527: Background music fails to play +Bug #533: The arch at Gnisis entrance is not displayed +Bug #534: Terrain gets its correct shape only some time after the cell is loaded +Bug #536: The same entry can be added multiple times to the journal +Bug #539: Race selection is broken +Bug #544: Terrain normal map corrupt when the map is rendered +Feature #39: Video Playback +Feature #151: ^-escape sequences in text output +Feature #392: Add AI related script functions +Feature #456: Determine required ini fallback values and adjust the ini importer accordingly +Feature #460: Experimental DirArchives improvements +Feature #540: Execute scripts of objects in containers/inventories in active cells +Task #401: Review GMST fixing +Task #453: Unify case smashing/folding +Task #512: Rewrite utf8 component + 0.20.0 Bug #366: Changing the player's race during character creation does not change the look of the player character